前端高频面试题小汇总

147 阅读16分钟

JS数据类型

  • 基本类型: number、string、boolean、null、undefined、symbol
  • 引用(复合类型)类型: Object(函数、对象、数组等)

不同的存储方式:

  • 基本类型:基本类型的值在内存中占据固定大小,保存在栈内存中
  • 引用类型:引用类型的值是对象,保存在堆内存中,而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址

symbol类型介绍

Symbol对象是es6中新引进的一种数据类型,它的作用非常简单,就是用于防止属性名冲突而产生。还可以实现属性的私有化(private).

Symbol的最大特点就是值是具有唯一性,这代表使用Symbol类型的值能做独一无二的一些事情.

此外,Symbol没有构造函数,这使得我们不能new它,直接使用即可.

symbol类型作用:

  1. 设置对象唯一键(防止对象合并属性名冲突)

    Js中的对象(键)如果直接声明就会变成String类型,这在某些程度上可能会引起对象属性冲突问题。

    对象的键最好是唯一的,Symbol类型的值无疑是最好的选择。

    当我们给想对象的键设置为Symbol类型的值的时候需要注意2点问题。

    let info1 = {
        name: '丽丽',
        age: 24,
        job: '公司前台',
        [Symbol('description')]: '平时喜欢做做瑜伽,人家有男朋友,你别指望了'
    }
    let info2 = {
        [Symbol('description')]: '这小姑娘挺好的,挺热情的,嘿嘿嘿……'
    }
    
  2. 作为对象私有属性,来模拟private效果

我们可以使用Symbol不能被for/in以及for/of访问的特性,为类制作私有属性以及提供访问接口。

Symbol声明和访问使用 [](变量)形式操作

 不能使用 . 语法因为 .语法是操作字符串属性的

相关参考:

this指向问题

记住一句话:this永远指向最后的调用者

var obj = {
	name: "24"
	getName: function(){
	    console.log(this)
	    console.log('名字' + this.name)
	}
}

obj.getName(); // this 指向obj
var func = obj.getName;
func(); //  this 指向 window 实质上是全局window对象在调用 

DOM中的this:

<body>
    <!-- this: 代表当前绑定的标签DOM对象 -->
    <button onclick="foo(this)">btn</button>
</body>
<script>  
    function foo(ele){
        console.log(ele.innerHTML);
    }
</script>

Jquery中的this:

$("#box).click(function(){
    console.log(this);  //  代表当前绑定的标签DOM对象
    // 转出jquery对象
    console.log(  $(this) )
})

Vue单文件中的this

this代表当前组件的components

改变this指向

通过 bind/call/apply

语法:

函数名.bind(对象,参数1,....)
函数名.call(对象,参数1,....)
函数名.apply(对象,[参数1,....]])

三种相同点: 都可以改变函数内this的指向

不同点: call/apply是立即执行,bind不会立即执行,后面需要加()才执行

call和apply:

  • call参数一个一个传递

  • apply传递数组

作用域和作用域链

  • 作用域

定义了标识符(变量名、函数名)的有限访问范围

仅有函数才会产生局部作用域

  • 什么是作用域链

函数内部访问函数外部的标识符(变量名,函数名),首先会在当前作用域中查找,若找不到,则去父级作用域中找,直到找到为止,这种按链式结构查找的过程,就是作用域链

原型和原型链

  • 什么是原型

每个函数或对象都有__proto__属性 它的值是一个对象 即原型对象

  • 什么是原型链

当对象使用属性的时候,先在自身进行查找,有就直接使用,若没找到就会沿着__proto__属性往上找,直到找到Object原型对象,这种寻找过程就是原型链

  • 你有使用过哪些继承方法

  • for-in

  • call/apply: 去父类构造函数中伪造this,把构造函数属性添加子类对象的自身空间中

  • 原型继承: 继承父类原型中的属性或方法,核心还是父类的对象__proto__找到父类的原型

  • 子类.prototype = new 父类(); 

  • 组合继承(call+原型继承)

    // 寻找aaa方法过程 var str = "hello"; str.aaa();

    1. 去String.prototype中去找 2. 上面找不到,则继续沿着String.prototype的__proto__ 属性找到Object.prototype中去寻找 Object.prototype.proto === null // true

    var obj = {}; obj.bbb();

    // 直接去到Object.prototype中去找bbb方法 console.log(obj instanceof Object); // true

闭包-Closure(重点)

  • 什么是闭包(谈下你对闭包的理解)

  • 概念:闭包就是能够读取其他函数内部变量的函数。

  • 三大特性:

    • 函数嵌套函数。

    • 函数内部可以引用外部的参数和变量。

    • 参数和变量不会被垃圾回收机制回收。

  • 怎么样产生闭包?

  • 当某个函数的作用域链还引用着其他函数的活动对象时,就会形成闭包

  • 闭包优缺点

  • 优点

  • 希望一个变量长期存储在内存中。

  • 避免全局变量的污染。

  • 私有成员的存在。

  • 缺点

  • 常驻内存,增加内存使用量。

  • 使用不当会很容易造成内存泄露

  • 你在哪些场景使用过闭包,即用闭包解决过哪些实际问题

  1. 单例模式

  2. 事件性能优化-防抖节流 参考

    函数防抖(debounce)和函数节流(throttle)都是为了缓解函数频繁调用,它们相似,但有区别.
    
    函数节流:在设定的时间间隔内只会执行一次任务.
    如果在同一个单位时间内某事件被触发多次,只有一次能生效
    
    函数防抖:只有任务触发的时间间隔超过设定的间隔后,任务才会执行一次。
    如果在这时间间隔内又被触发,则重新计时。
    
    - 节流主要用在鼠标事件上
    - 防抖主要用在键盘事件上
    
  3. 模块化编程(jquery库)

  4. 函数科里化

  5. 解决for循环引用问题, for中使用延时器setTimeout

函数科里化(理解)

概念:

柯里化,可以理解为提前接收部分参数,延迟执行,不立即输出结果,而是返回一个接受剩余参数的函数

题目:实现 add(1)(2, 3)(4)() = 10 的效果

依题意,有两个关键点要注意:

  • 传入参数时,代码不执行输出结果,而是先把所有参数先用闭包记忆起来

  • 当传入空的参数时,代表可以进行真正的运算

完整代码:

function currying(fn){
    var allArgs = []; //闭包变量 用来接受所有的参数

    return function next(){
      	// 将伪数组转为真数组,
        // var args = Array.prototype.slice.call(arguments);
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args); // 传参了,返回闭包函数
            return next;
        }else{
            return fn.apply(null, allArgs); // 无参数,执行传入的函数
        }
    } 
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});

柯里化,在这个例子中可以看出很明显的行为规范:

  • 逐步接收参数,并缓存供后期计算使用

  • 不立即计算,延后执行

  • 符合计算的条件,将缓存的参数,统一传递给执行方法

相关文档:

对深浅拷贝(克隆)的理解,如何实现?

深拷贝 & 浅拷贝
浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响

相关参考

也可以借助第三方库实现,如 lodash 函数工具库的以下两个办法

_.clone(value) :  创建一个 value 的浅拷贝(只拷贝第一层)
_.cloneDeep(value): 递归拷贝 value。(注:也叫深拷贝)

数组中的reduce方法

mdn reduct参考

  • reduce方法接受一个函数作为累加器(“连续操作器”)

  • reduce为数组中的每一个元素依次执行回调函数。最终返回最后一次调用累加器的结果

  • 语法:

    arr.reduce(callback(accumulator, currentValue,[index],[array]), initialValue)

callback四个参数:

  • accumulator 累计器

  • currentValue 当前值

  • currentIndex 可选,当前索引

  • array 可选,数组

  • initialValue可选,累加器函数默认值

**注意:**如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始

代码示例:

var arr = [1,2,3,4];
z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z
var result = arr.reduce(function (accumulator, currentValue, index) {
    // console.log(accumulator, currentValue, index)
    return accumulator + currentValue;
});

console.log(result) // 10 

var result = arr.reduce(function (accumulator, currentValue, index) {
    // console.log(accumulator, currentValue, index)
    return accumulator + currentValue;
},10);

console.log(result) // 10                                                                        

什么是跨域,为什么会有跨域限制,如何解决跨域?

  1. 所谓的跨域就是浏览器为了安全起见,而采用的同源策略(Same origin policy),阻止获取不同源中的数据信息。

  2. 同源略是 Netscape (网景公司)提出的一个著名的安全略,所有支持 Javascript 的浏览器都会使用这个策略,

  3. web 是构建在同源策略之上的,浏览器只是针对同源策略的一种实现

  4. 所谓同源是指:协议、域名(IP),端口号 要一致才行。这样才算是在同一个域里

  5. 如何才算跨域,请求与被请求的协议、域名、端口有不一样则就是跨域

非同源受到哪些限制?

  1. Cookie不能读取

  2. DOM无法获取

  3. Ajax请求不能发送

没有同源策略的危险场景

危险场景:有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进 www.yinghang.com 改密码,点击去之后网页会有类似下面代码:

<iframe id='baidu' src='https://www.baidu.com'></iframe>

<script>
	let iframe = window.frames['baidu']
	let value = iframe.document.getElementById('含有敏感信息的input框的id,如密码,银行账号')
</script>

iframe标签 非常危险,基本不用

如何解决跨域问题(三种)

  1. 代理

  2. cors。服务端设置响应头允许跨域。需要服务端配合

  3. jsonp。 jsonp不是一种技术,它是程序员 智慧的结晶

    • 原理:利用script标签的src属性不受同源策略限制的特点,服务端响应一个符合js函数调用语法的字符串。前端执行函数,从而达到跨域。

    • 局限性: 只能发送get请求,必须后端配合

开发环境:代理proxy、cors

生产(线上)环境:nginx代理、cors

前端代理: vue.config.js

module.exports = {
    // 只适用于开发环境
    devServer: {
      proxy: {
        '/api': {
          target: 'http://api.w0824.com/',
          ws: true,
          // changeOrigin 设置为true后,target才可以是一个域名
          changeOrigin: true

        },
        '/wx': {
            target: 'http://api.wx.com/',
            ws: true,
            // changeOrigin 设置为true后,target才可以是一个域名
            changeOrigin: true

          }
      }
    }
  }

请求:

import axios from 'axios';
export default {
  name: 'App',
  methods:{
    async getLunbo(){
      // var apiulr = "http://api.w0824.com"
      var res = await axios.get('/api/getlunbo');
      console.log(res)
    }
  }
}

异步发展历程

  • ① var xhr = new XMLHttpRequest ActiveXObject('Microsoft.XMLHTTP')

    get-4步骤, post-五部

    var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){

    if(this.readyState == 4 && this.status == 200){
    	// data '{"name":"kobe"}'   '[{"name":"kobe"},{"name":"jame"}]' 
        var data = this.resonseText(); // data json字符串 
        var result = JSON.parse(data)
        // 把数据做一些业务逻辑
    }
    

    } xhr.open('post','url',true) // 模拟post表单传递数据 xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded') xhr.send();

  • ② jquery-ajax .get​.post

    $.ajax()实现文件上传

    原生post请求传递二进制和文本数据,核心post,设置请起头content-type为multipart/form-data

    $.ajax({ type:'post', data: new FormData(表单dom对象), processData: false, # 代表不处理数据 contentType: false, #设置内容类型 multipart/form-data success:function(){

       }
    

    })

get也可以实现文件上传,但要把图片文件变成base64编码即可 img src="base64"

base64一般只需要把小图变成base64即可 webpack js .css-loader file-loader?limit=2048

  • ③ Promise

  • promise解决了什么问题 ? 解决了回调地狱问题

  • 什么是回调地狱:回调函数会层层嵌套,导致可读性差

  • 如何把原生ajax封装成一个promise版本

  • ④ window.fetch (仅浏览器支持,返回一个promise)

  • ⑤ async/await 异步终极解决方案  (async/await本质是生成器* yield的语法糖形式)

    var promiseArr = []; console.time('query time') arr.map(async (v)=>{ promiseArr.push( query(v.sql) ) }) // 并行执行多个异步请求,效率更高 var result = Promise.all(promiseArr) console.endTime('query time')

生成器generator

Generator函数的强大在于**允许你通过一些实现细节来将异步过程隐藏起来,**依然使代码保持一个单线程、同步语法的代码风格。这样的语法使得我们能够很自然的方式表达我们程序的步骤/语句流程,而不需要同时去操作一些异步的语法格式

示例一:

function* myGenerator(){
    yield 1; // yield 提前返回
    yield 2;
    yield 3;
}

var gen = myGenerator();
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())
// console.log(gen.next())

// var result;
// while(!(result = gen.next()).done){
//     console.log(result.value)
// }

// for(var i of gen){
//     console.log(i)
// }

示例2: 生成器和异步的结合

Generator函数的强大在于允许你通过一些实现细节来将异步过程隐藏起来,依然使代码保持一个单线程、同步语法的代码风格。这样的语法使得我们能够很自然的方式表达我们程序的步骤/语句流程,而不需要同时去操作一些异步的语法格式。

也就是说,Generator 函数相当于就是一个异步操作的容器

<script>
    function* myGenerator(url) {
        yield fetch(url);
    }

    var gen = myGenerator('http://api.w0824.com/api/getlunbo');

    /*
    	// 或者写成函数自执行形式
        var gen = (function * (url){
            yield fetch(url);
        })('http://api.w0824.com/api/getlunbo')
	*/

    var promise = gen.next().value
    promise
        .then(res => res.json())
        .then(data => {
            console.log(data)
        })
</script>

nodejs中可以借助axios发送异步请求:

var axios = require('axios');
console.log('1')
    var gen = (function * (url){
        yield axios.get(url);
    })('http://api.w0824.com/api/getlunbo')

    var promise = gen.next().value
    promise.then(res => {
            console.log(res.data)
     })
console.log('3')

Co函数库

什么是 co 函数库

co 函数库 是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。

co 函数库可以让你不用编写 Generator 函数的执行器(即不用写next) 

var co = require('co');
co(gen);

上面代码中,Generator 函数只要传入 co 函数,就会自动执行

co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数

co(gen).then(function (){
  console.log('Generator 函数执行完成');
})

基本使用:

var axios = require('axios');
var co = require('co')

var gen = function * (url){
    var result =  yield axios.get(url);
    return result;
};

co(gen('http://api.w0824.com/api/getlunbo')).then(res => {
    console.log(res.data)
})

async和await终极解决方案

async/await本质是 生成器* yield的语法糖形式,书写起来更加便捷

var axios = require('axios');
var co = require('co')

var gen = async function  (url){
    var result =  await axios.get(url);
    return result;
};

co(gen('http://api.w0824.com/api/getlunbo')).then(res => {
    console.log(res.data)
})

事件循环 event loop 宏任务 微任务

  • js是单线程的,nodejs也是一样,基于chrome的v8引擎实现

  • 同步任务会直接进入主线程依次执行

  • 异步任务会再分为宏任务和微任务

执行顺序:主线程中的同步代码执行完毕后,只剩下微任务和宏任务,顺序按照先微后宏来执行

function test() {
    console.log(1)
    setTimeout(function () { // timer1
        console.log(2)
    }, 1000)
}

test();

setTimeout(function () { // timer2
    console.log(3)
})

new Promise(function (resolve) {
    console.log(4)
    setTimeout(function () { // timer3
        console.log(5)
    }, 100)
    resolve()
}).then(function () {
    setTimeout(function () { // timer4
        console.log(6)
    }, 0)
    console.log(7)
})

console.log(8)

// 结果: ?

补充关于 async/await 函数

setTimeout(() => console.log(4)) //(宏任务)

async function test() {
    console.log(1); // await前: 相当于 new Promise构造函数中的同步代码
    await Promise.resolve()
    console.log(3); // await后:  相当于 Promise.then 中的异步代码(微任务)
}

test()

console.log(2);

// 结果 ?  1 2 3 4

从地址栏输入一个url,到看到页面,中间经历过了哪些过程?

大致流程

  1. URL 解析

  2. DNS(Domain Name System) 查询

  3. TCP 连接

  4. 处理请求

  5. 接受响应

  6. 渲染页面

参考:在浏览器输入 URL 回车之后发生了什么(超详细版)

什么是协议缓存和强缓存

对于IE来说:
	只要请求的地址不发生变化,那么直接走强缓存。
对于chrome和firefox
	只要请求的地址不发生变化,尝试请求协商缓存 - 304

相关参考:

http协议和https协议

相关参考:

http状态码:

  • 1xx: 指示信息,表示请求已经接收,继续处理。

  • 2xx: 成功,表示请求已经被成功接收。

  • 3xx: 重定向,要完成请求必须进行更近一步操作

  • 4xx: 客户端错误,请求有语法错误或请求无法实现

  • 5xx: 服务端错误,服务器未能实现合法的请求

常见状态码:

  • 200——表明该请求被成功地完成,所请求的资源发送回客户端

  • 304——自从上次请求后,请求的网页未修改过,请客户端使用本地缓存(命中了协商缓存)

  • 400——客户端请求有错

  • 401——请求未经授权

  • 403——禁止访问

  • 404——资源未找到

  • 500——服务器内部错误

  • 503——服务不可用

常用的请求头部(部分):

  • Accept: 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type)

  • Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收

  • Content-Type:客户端发送出去实体内容的类型

  • Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache

  • If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内,http1.0中

  • Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,而且是服务端时间

  • Max-age:代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中

  • If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(非常精确),http1.1中

  • Cookie:有cookie并且同域访问时会自动带上

  • Connection:当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive

  • Host:请求的服务器URL

  • Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私

  • Referer:该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址,csrf拦截常用到这个字段)

  • User-Agent:用户客户端的一些必要信息,如UA头部等

常用的响应头部(部分):

  • Access-Control-Allow-Headers: 服务器端允许的请求Headers

  • Access-Control-Allow-Methods: 服务器端允许的请求方法

  • Access-Control-Allow-Origin: 服务器端允许的请求Origin头部(譬如为*)

  • Content-Type:服务端返回的实体内容的类型

  • Date:数据从服务器发送的时间

  • Cache-Control:告诉浏览器或其他客户,什么环境可以安全的缓存文档

  • Last-Modified:请求资源的最后修改时间

  • Expires:应该在什么时候认为文档已经过期,从而不再缓存它

  • Max-age:客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效

  • ETag:请求变量的实体标签的当前值

  • Set-Cookie:设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端

  • Keep-Alive:如果客户端有keep-alive,服务端也会有响应(如timeout=38)

  • Server:服务器的一些相关信息

    一般来说,请求头部和响应头部是匹配分析的, 如:请求头部的 Accept要和响应头部的 Content-Type匹配,否则会报错。跨域请求时,请求头部的 Origin要匹配响应头部的 Access-Control-Allow-Origin,否则会报跨域错误在使用缓存时,请求头部的 If-Modified-Since、 If-None-Match分别和响应头部的 Last-Modified、 ETag对应

TCP协议连接三次握手和断开四次挥手

1. tcp连接三次握手的意义:

获取到服务器的IP, 三次握手才能确认双方的接收与发送能力是否正常。

简单理解:

  • 第一次握手:由浏览器发给服务器,我想和你说话,你能 "听见" 吗?

  • 第二次握手:由服务器发给浏览器,我能听见,你说吧!

  • 第三次握手:由浏览器发给服务器,好,那我开始说话了。

2. tcp断开连接四次挥手的意义:

确保数据的完整性。

简单理解:

  • 第一次挥手:由浏览器发给服务器,我的东西接受完了,你关闭吧。

  • 第二次挥手:由服务器发给浏览器,我还有一些东西没接收完,你等一会,我接收好了我告诉你。

  • 第三次挥手:由服务器发给浏览器,我接收完了,你断开吧!

  • 第四次挥手:由浏览器发给服务器,好的,那我断开了。

相关参考:面试官,不要再问我三次握手和四次挥手

web性能优化有哪些手段,具体怎么实现

  • 避免重排和重绘
  • 等等......

简述mvvm的原理

  • vue2 数据劫持 Object.defineproperty

  • vue3 采用代理 es6-proxy

相关参考:

Nodejs中的Buffer

文件上传

vue