前端面试之道总结

面试中被问到的问题

工作中的亮点和难点

遇到的技术难点 

做组长干了什么 (带员工、任务分发、技术问题解决也负责过完整项目)

系统优化  

setState 内部渲染机制

自适应布局

垂直居中

原型链

遇到过兼容性的问题吗,要如何处理

跨域以及如何处理的

你为什么选择前端

如何跨域

谈一下webpack使用

react原理、单向数据流

浏览器异步

redux代码实现、原理

ES6中用过什么、es6新特征  

let, const局部变量 、箭头函数 、 类以及继承 、 模板字符串 、 解构赋值 、 export和import

flex细节

讲讲Promise

解决异步回调深层次嵌套带来的阅读和维护性困难,Promise实现了异步数据获取和业务逻辑分离。我们可以用同步的方式去写异步,并且可以通过链式调用执行深层次的使用

原型方法then\catch\finally       all\race包装多个实例

 

小册面试之道总结

判断数据类型

typeof 、instanceof

typeof 对于原始类型来说,除了 null 都可以显示正确的类型

instanceof 内部机制是通过原型链来判断所以不能用于基本类型

Object.prototype.toString.call() 对所有类型都可以判断

Array.isArray() 单独用来判断数组

 

类型转换

Es6 Object.is()用法和 ‘===’相同,除了两个特殊 0 不等于 -0、NaN 等于 NaN

 

this

对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window

对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo函数中的 this 就是 obj 对象

对于 new 的方式来说,this 被永远绑定在了 对象上面,不会被任何方式改变 this

箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this

 

闭包  (注意面试官问的函数内嵌函数变量保留问题)

闭包的定义

函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

作用

利用直接执行的函数模仿块级作用域,保存外部函数的变量

//利用闭包累加

function addCount() {
   
var count = 0;
    return function
() {
        count++
;
        return
count
    }
}

var fn = addCount();

 

//利用闭包保存i的值

for (var i = 0; i < 4; i++){
   
setTimeout((function () {
        console.
log(i);
   
})(i), 300)
}

 

原型 (硬背整个过程)

 

Es6 (待补充内容太多)  https://juejin.im/post/5e4943d0f265da57537eaba9

var、let 及 const 区别  (注意Var变量提升)

函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部

var 存在提升,我们能在声明之前使用。letconst 因为暂时性死区的原因,不能在声明前使用

var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会

let 和 const 作用基本一致,但是后者声明的变量不能再次赋值

let 和 const不允许重复声明

 

原型继承和 Class 继承

模块化

 

Promise实现

特点

对象的状态不受外界影响、一旦状态改变,就不会再变,任何时候都可以得到这个结果

优势

以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易

劣势

首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

方法(将方法写在原型上,使创建的对象共享方法,减少了内存的开销)

then:

Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数 

catch

相当于then第二个参数, 用于指定发生错误时的回调函数。

finally:

用于指定不管 Promise 对象最后状态如何,都会执行的操作。

all:

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

race:

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

 

Event Loop 事件循环

执行上下文、执行栈、宏任务、微任务

执行栈 Call Stack

一个存储函数调用的栈结构,遵循先进后出的原则

什么是 Event Loop

  • 首先执行同步代码,这属于宏任务
  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如有必要会渲染页面
  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

 

浏览器相关

事件机制

事件触发有三个阶段

window 往事件触发处传播,遇到注册的捕获事件会触发

传播到事件触发处时触发注册的事件

从事件触发处往 window 传播,遇到注册的冒泡事件会触发

注册事件

通常我们使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false ,useCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性

capture:布尔值,和 useCapture 作用一样

once:布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听

passive:布尔值,表示永远不会调用 preventDefault

一般来说,如果我们只希望事件只触发在目标上,这时候可以使用 stopPropagation 来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。

 

事件代理

将事件委托给它们父级代为执行事件,React就利用事件代理提高性能(为什么能优化性能呢? 减少与Dom的交互,减少浏览器的重绘、重排)

跨域

什么是跨域、为什么?

浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。

那么是出于什么安全考虑才会引入这种机制呢? 其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。

跨域方式1 JSONP

利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>

<script>

    function jsonp(data) {

    console.log(data)

    }

</script>

跨域方式2 CORS (重点,项目中用的就是这个,nginx反向代理也看看吧)

 

跨域方式3 document.domain

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。

只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域

跨域方式4 postMessage

这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

其他跨域方式(待补充)

Websocket 代理 webpack

 

存储

cookie,localStorage,sessionStorage,indexDB

 

 

 

Service Worker (待补充)

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

浏览器缓存机制

缓存位置

Service Worker

Memory Cache

Memory Cache 也就是内存中的缓存,读取内存中的数据肯定比磁盘快。但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放

Disk Cache

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据

缓存流程 (不太确定)

1.先查看 memory cache, 有资源,则返回!如果没有

2.查看 disk cache。如果有命中缓存(强缓存且不过期),则返回,如果没有命中缓存,则

3.发送网络请求,拿到了响应结果(协商缓存),把资源存入 disk cache, 并且把资源的引用存入memory cache

缓存策略 (可补充)

强缓存

Expires

Expires 是 HTTP/1 的产物,表示资源会在 一段时间 后过期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

Cache-Control

出现于 HTTP/1.1,优先级高于 Expires

协商缓存

如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 

Response  Last-Modified 和 Request If-Modified-Since

保存本地文件最后修改日期,服务器查询该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。

 

Response  ETag 和 Request  If-None-Match

类似于文件指纹,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来

实际场景应用缓存策略

对于频繁变动的资源,首先需要使用 Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效

对于打包之后的js文件设置缓存有效期,只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。


浏览器渲染原理

为什么操作 DOM 慢

因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

重绘(Repaint)和回流(Reflow)

重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘

回流是布局或者几何属性需要改变就称为回流。

减少重绘和回流

将多个样式操作合并

不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

不要把节点的属性值放在一个循环里当成循环里的变量

如何更快的渲染出界面

缓存

减小文件大小

使用defer,async加载js文件

扁平层级,优化选择器 CSS 选择符从右往左匹配查找,避免节点层级过多

按需加载js文件

性能优化琐碎事

图片优化

懒加载react-lazyload

小图使用 base64 格式

节流(一段时间之内只执行一次,滚动事件中会发起网络请求

防抖(按钮连点, 在规定时间禁止事件执行,如果规定时间内事件再次执行,以此事件执行为起规定时间内禁止执行)

 

 

 

 

Es6

重看Es6结合其在React中产生的影响

es6对模块化开发的影响

 

为什么函数组件内 建议使用函数表达式而非函数声明

在块级作用域内声明函数,在作用域外调用此函数在es5、es6环境下结果不同,具体为es5环境下函数会整体提升,es6环境下其效果类似var 只预先声明变量名没有值。利用函数表达式指定其为let或var就没有此问题。

为什么js中声明的块级变量只能在此类中使用

全局变量与顶层对象分离

ES6 规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

实际例子,React中我们直接在js文件中定义的let、const变量在class内可以使用,但其作用域仅限此js文件,其他文件无法使用不属于顶层对象。

在js文件中而非class中定义常量的好处

在js文件被多次调用的情况下,在js文件中声明常量比在class中声明常量更好,原因在于js中声明的常量是被公用的,即在多次被引用时使用同一内存地址而如果在class中声明,每次使用js文件,常量就会被创建增加开销。

       解构赋值,提供另一种数组裁剪方式

let [head, ...tail] = [1, 2, 3, 4];

head // 1

tail // [2, 3, 4]

      

 

 

Promise简单实现

 
const PENDING = 'pending';

const RESOLVED = 'resolved';

const REJECTED = 'rejected';



function MyPromise(fn) {

    const _this = this;

    _this.value = null;

    _this.state = PENDING;

    _this._resolvedCallbacks = [];

    _this._rejectedCallbacks = [];



    function resolved(res) {

        if (_this.state === PENDING) {

            _this.state = RESOLVED;

            _this.value = res;

            _this._resolvedCallbacks.map(cb => cb(_this.value))

        }

    }

    function rejected(e) {

        if (_this.state === PENDING) {

            _this.state = REJECTED;

            _this.value = e;

            _this._rejectedCallbacks.map(cb => cb(_this.value))

        }

    }



    try {

      fn(resolved, rejected)

    } catch (e) {

      rejected(e)

    }

}



MyPromise.prototype.then = function (onSuccess, onFailure) {

    const _this = this;

    onSuccess = typeof onSuccess === 'function' ? onSuccess : v => v;

    onFailure = typeof onFailure === 'function' ? onFailure: v => { throw(v) };

    if (_this.state === PENDING) {

        _this._resolvedCallbacks.push(onSuccess);

        _this._rejectedCallbacks.push(onFailure)

    }

    if (_this.state === RESOLVED) {

        onSuccess(_this.value)

    }

    if (_this.state === REJECTED) {

        onFailure(_this.value)

    }

};

 

call、apply、bind函数

实现思路:将this指向一个函数,并提供参数

 
Function.prototype.myCall = function (obj) {

  if (typeof this !== 'function') {

      throw new TypeError('Error')

  }

  obj = obj || window;

  obj._fn = this;

  const params = [...arguments].slice(1);

  const result = obj._fn(...params);

  delete obj._fn;

  return result

};



Function.prototype.myApply = function (obj) {

    const params = arguments[1]; //自己实现时与call的区别。 实际call、apply上不同,在参数较少时call性能更好一些

};



Function.prototype.myBind = function (context) {

    const _this = this;

    const params = [...arguments].slice(1);

    return function F() {

        if (this instanceof F){

            return new _this(...params, ...arguments)

        } else {

            return _this.apply(context, params.concat(...arguments))

        }

    }

};

 

实现new

 
function create() {

    const obj = {};

    const Con = [].shift.call(arguments);

    obj._proto_ = Con.prototype;

    let result = Con.apply(obj, arguments);

    return result instanceof Object ? result : obj

}

 

继承

function Parent(value) {

  this.val = value

}

Parent.prototype.getValue = function() {

  console.log(this.val)

}

function Child(value) {

  Parent.call(this, value)

}

Child.prototype = new Parent()

 

const child = new Child(1)

 

child.getValue() // 1

child instanceof Parent // true

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值