前端面试题总结-补01

35 minute read

前端面试题第二波 第一波

实习相关

主要记录一下当面试官问道暑期实习的相关问题时,该怎么回答。

介绍一下实习期间的工作

(2018年美团实习)实习期间主要负责的是B端的各类工作,包括给统计每天各城市、各商家销售情况的统计图表类项目,主要的工作内容是页面和前端接口的编写;以及还有实习最后阶段开发用于内部负责数据分析工作同事使用的实现类似自定义页面的系统。

实习期间遇到的主要问题

主要问题是新框架的适应问题,实习期间用到了一种接口描述语言“thrift”(Thrift包含一套完整的栈来创建客户端和服务端程序。顶层部分是由Thrift定义生成的代码。而服务则由这个文件客户端和处理器代码生成。在生成的代码里会创建不同于内建类型的数据结构,并将其作为结果发送。协议和传输层是运行时库的一部分。有了Thrift,就可以定义一个服务或改变通讯和传输协议,而无需重新编译代码。除了客户端部分之外,Thrift还包括服务器基础设施来集成协议和传输,如阻塞、非阻塞及多线程服务器。栈中作为I/O基础的部分对于不同的语言则有不同的实现),还有就是开发过程中会用到一些小组内部重构或整合的UI库,随着react版本的迭代,很多库都变得老化臃肿,所以开发过程中还需要不断地踩坑填坑。(待补充,一两个react组建的坑)

实习期间最大的收获是什么

实习期间最大的收获首先是认识了很多不错的同事,同事会进行code view,所以经过实习之后,会更注意代码规范。

Javascript

原生js解析cookie

function getCookie(name)
{
    var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
    if(arr=document.cookie.match(reg))
        return unescape(arr[2]);
    else
        return null;
}

正则解释:^| name表示匹配开始或者匹配空格,=([^;]*)匹配‘=’加上后面的一直的非‘;’,最后 (;|$)匹配最后的‘;’或最后结束

jS实现兼容的div拖放功能

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>tmp.html</title>
    <style>
        .content {
            background-color: #bfbfbf;
            height: 500px;
        }
        .obj {
            position: absolute;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
            border: 1px solid green;
            background-color: orange;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="content" id="obja"></div>
        <div class="obj" id="objb"></div>
        <p id="para"></p>
    </div>
    <script>
        var flag = false;
        var sx, sy, cx, cy, dx, dy;
        var obj = document.getElementById("objb");
        if(obj.addEventListener) { // 除IE8已下都可以
            obj.addEventListener('mousedown', function(event) {dragStart(event);}, false);
            obj.addEventListener('mousemove', function(event) {dragMove(event);}, false);
            document.body.addEventListener('mouseup', function(event) {dragEnd(event);}, false);
        }
        else if(obj.attachEvent){
            obj.attachEvent('onmousedown', function(event) {dragStart(event);});
            obj.attachEvent('onmousemove', function(event) {dragMove(event);});
            document.body.attachEvent('onmouseup', function(event) {dragEnd(event);});
        } else {
            obj['onmousedown'] = function(event) {dragStart(event);}
            obj['onmousemove'] = function(event) {dragMove(event);}
            obj['onmouseup'] = function(event) {dragEnd(event);}
        }
        function dragStart(event) {
            flag =true;
            sx = event.clientX;
            sy = event.clientY; // client是moseevent,所以sx,sy为鼠标的位置,要判断鼠标在框框内
            dx = obj.offsetLeft;
            dy = obj.offsetTop;
        }
        function dragMove(event) {
            if(flag) {
                cx = event.clientX;
                cy = event.clientY;
                obj.style.left = cx - sx + dx + 'px';
                obj.style.top = cy - sy + dy + 'px';
                if(event.preventDefault) { // 可加可不加
                    document.addEventListener("mousemove", function(){
                        event.preventDefault(); // 默认动作不要做                 
                    }, false); // false代表事件冒泡,true代表事件捕获
                } else {
                    document.attachEvent("onmousemove", function(){
                        window.event.returnValue = false;
                    });
                }
            }
        }
        function dragEnd(event) {
            flag = false;
        }
    </script>
</body>
</html>

css实现tab切换

关键代码是label的for标签,这样相当于可以和for绑定的radio有一个联动

<div class="tabs">
  <div class="tab-pane">
    <input type="radio" name="tab" id="tab01" checked />
    <label class="tab-item" for="tab01">tab01</label>
    <div class="tab-content">111</div>
  </div>
  <div class="tab-pane">
    <input type="radio" name="tab" id="tab02"/>
    <label class="tab-item" for="tab02">tab02</label>
    <div class="tab-content">222</div>
  </div>
  <div class="tab-pane">
    <input type="radio" name="tab" id="tab03"/>
    <label class="tab-item" for="tab03">tab03</label>
    <div class="tab-content">333</div>
  </div>
</div>

各种选择器,注意+是选择紧接在xx元素之后的所有xx元素。

.tabs{
  position:relative;
  width:400px;
  height:300px;
}
.tab-pane{
  display:inline-block;
}
.tabs input[type='radio']{
  position:absolute;
  clip:rect(0,0,0,0)
}
.tab-item{
  display:block;
  height:34px;
  line-height:34px;
  cursor:pointer;
  padding:0 10px
}

.tabs input[type='radio']:checked+.tab-item{
  background:orangered;
  color:#fff
}

.tab-content{
  position:absolute;
  border:1px solid #eee;
  padding:20px;
  left:0;
  top:36px;
  bottom:0;
  right:0;
  background:#fff;
}

.tabs input[type='radio']:checked+.tab-item+.tab-content{
  z-index:1
}

原生实现autocomple

 <div id="div1">
    <input type="text" id="input" placeholder="有autocomplete的输入框"/>
    <ul id="ul">
    </ul>
</div>
*{
    margin: 0;
    padding: 0;
    box-sizing:border-box;
}
ul{
    list-style: none;
    margin: 0 auto;
    background-color: #ededed;
    color: #3b200c;
    width: 400px;
    border: none;
}
li{
    cursor: pointer;
}
input{
    display: block;
    margin: 0 auto;
    line-height: 40px;
    height: 40px;
    width: 400px;
    font-size: 20px;
}
var arr = ['a','apple','abandon','bilibili','beep','before','become','being','highmaintains','by','bye','banana']
input.addEventListener('input', function(event){
    var _value = event.target.value.trim()
    if(_value){
        autoComplete(_value, arr)
    }
    else{
        ul.innerHTML = ''
    }
})
function autoComplete(str, arr){
    var lis = []
    arr.forEach((word)=>{
        if(word.startsWith(str)){
            lis.push('<li>' + word + '</li>')
        }
    })
    ul.innerHTML = lis.join('')
}
function addToInput(li){
    var _txt = li.innerText
    input.value = _txt
}
ul.addEventListener('click', function(event){
    if(event.target.tagName.toLowerCase() === 'li'){
        addToInput(event.target)
    }
})

原生实现event类

  • on(event,fn):监听event事件,事件触发时调用fn函数;

  • once(event,fn):为指定事件注册一个单次监听器,单次监听器最多只触发一次,触发后立即解除监听器;

  • emit(event,arg1,arg2,arg3…):触发event事件,并把参数arg1,arg2,arg3….传给事件处理函数;

  • off(event,fn):停止监听某个事件。

class EventEmitter{
    constructor(){
        this._events={}
    }
    on(event,callback){
        let callbacks = this._events[event] || []
        callbacks.push(callback)
        this._events[event] = callbacks
        return this
    }
    off(event,callback){
        let callbacks = this._events[event]
        this._events[event] = callbacks && callbacks.filter(fn => fn !== callback)
        return this
    }
    emit(...args){
        const event = args[0]
        const params = [].slice.call(args,1)
        const callbacks = this._events[event]
        callbacks.forEach(fn => fn.apply(this, params))
        return this
    }
    once(event,callback){
        let wrapFunc = (...args) => {
            callback.apply(this,args)
            this.off(event,wrapFunc)
        }
        this.on(event,wrapFunc)
        return this
    }
}

JS浅拷贝与深拷贝

  • 浅拷贝也就是拷贝引用;拷贝原对象的实例,但是对其内部的引用类型值,拷贝的是其引用,jQuery的一些方法Array.prototype.slice()Array.prototype.concat()都可以返回一个对象或者数组的浅拷贝。
function shallowClone(source) {
    if (!source || typeof source !== 'object') {
        throw new Error('error arguments');
    }
    var targetObj = source.constructor === Array ? [] : {};
    for (var keys in source) {
        if (source.hasOwnProperty(keys)) {
            targetObj[keys] = source[keys];
        }
    }
    return targetObj;
}
  • 深拷贝即拷贝实例,深拷贝拷贝出一个新的实例,新的实例和之前的实例互不影响。
// 递归实现一个深拷贝
function deepClone(source){
   if(!source || typeof source !== 'object'){
     throw new Error('error arguments', 'shallowClone');
   }
   var targetObj = source.constructor === Array ? [] : {};
   for(var keys in source){
      if(source.hasOwnProperty(keys)){
         if(source[keys] && typeof source[keys] === 'object'){
           targetObj[keys] = source[keys].constructor === Array ? [] : {};
           targetObj[keys] = deepClone(source[keys]);
         }else{
           targetObj[keys] = source[keys];
         }
      } 
   }
   return targetObj;
}

es5实现es6相关特性

首先是const:

var __const = function __const (data, value) {
    window.data = value // 把要定义的data挂载到window下,并赋值value
    Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
        enumerable: false,
        configurable: false,
        get: function () {
            return value
        },
        set: function (data) {
            if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
                throw new TypeError('Assignment to constant variable.')
            } else {
                return value
            }
        }
    })
}

然后还有super等,参考Js ES6中的”Super“和”Extends以及原文链接https://medium.com/@anurag.majumdar

ES6实现event类

  • on(event,fn):监听event事件,事件触发时调用fn函数
  • once(event,fn):为指定事件注册一个单次监听器,单次监听器最多只触发一次,触发后立即解除监听器;
  • emit(event,arg1,arg2,arg3…):触发event事件,并把参数arg1,arg2,arg3….传给事件处理函数;
  • off(event,fn):停止监听某个事件(就是把event队列里,叫fn的方法移除)。
    class EventEmitter{
      constructor(){
          this._events = {}
      }
      on(event,callback){  //监听event事件,触发时调用callback函数
          let callbacks = this._events[event] || []
          callbacks.push(callback)
          this._events[event] = callbacks
          return this
      }
      off(event,callback){  //停止监听event事件
          let callbacks = this._events[event]
          this._events[event] = callbacks && callbacks.filter(fn => fn !== callback)
          return this
      }
      emit(...args){ //触发事件,并把参数传给事件的处理函数
          const event = args[0]
          const params = [].slice.call(args,1)
          const callbacks = this._events[event]
          callbacks.forEach(fn => fn.apply(this.params))
          return this
      }
      once(event,callback){ //为事件注册单次监听器
          let wrapFanc = (...args) => {
              callback.apply(this.args)
              this.off(event,wrapFanc)
          }
          this.on(event,wrapFanc)
          return this
      }
    }
    

React

react生命周期

  1. componentWillMount()
  2. render()

在componentWillMount()方法之后 在componentWillReceive(nextProps, nextState)方法之后

  1. componentDidMount()
  2. componentWillReceiveProps(nextProps)
  3. shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate() 返回 false,这就意味着 componentWillUpdate()render()componentDidUpdate() 将不再执行

  1. componentWillUpdate(nextProps, nextState)

不可以调用setState()

  1. componentDidUpdate(prevProps, prevState)
  2. componentWillUnmount()

react context

context被翻译为上下文,当在组件树中不想通过props或者state的方式来传递数据时,可以使用context来实现跨层级组件传递,有很多react组件都是通过context来完成自己的功能,比如react-redux的<Provider />,就是通过context提供一个全局态的store

当使用propsstate时,数据是自顶向下流,也就是一层一层的传递,而context则可以跨越组件进行数据传递(其实也就导致了不稳定,所以官方并不推荐在稳定版本的react中使用)

如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider),通常是一个父节点,另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式(想想redux怎么用的)

react/redux设计模式

react/redux构架中观察到的最重要的一种结构模式是中介者模式,他允许我们公开一个统一的接口,系统不同部分可以通过该接口进行通讯。

react/redux这个组合中严格贯彻了中介者模式的思想,redux为单一state管理提供了解决方法。单一对象在处理复杂数据变化的时候可以协调指挥,不至于交叉变化引用。中介者可以处理多个委托人的请求,处理一个委托人的请求也不在话下。而且在增加委托者的时候对系统中其他委托者是没有影响的,他们都是独立存在的,只和中介者发生通讯联系。如果用单元测试的方法来测试也很容易。

mvvm设计模式

最常见的客户端架构有三种:

  • MVC: Model-View-Controller

View通过Controller来和Model联系,Controller是View和Model的协调者,View和Model不直接联系,基本联系都是单向的。

用户User通过控制器Controller来操作模板Model从而达到视图View的变化。

  • MVP: Model-View-Presenter

是从MVC模式演变而来的,都是通过Controller/Presenter负责逻辑的处理+Model提供数据+View负责显示。在MVP中,Presenter完全把View和Model进行了分离,主要的程序逻辑在Presenter里实现。并且,Presenter和View是没有直接关联的,是通过定义好的接口进行交互,从而使得在变更View的时候可以保持Presenter不变。

  • MVVM: Model-View-ViewModel

MVVM是把MVC里的Controller和MVP里的Presenter改成了ViewModel。Model+View+ViewModel。View的变化会自动更新到ViewModel,ViewModel的变化也会自动同步到View上显示。这种自动同步是因为ViewModel中的属性实现了Observer,当属性变更时都能触发对应的操作。

ES6

阮一峰,只记录一下难点。

Proxy & Reflect

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。

super、Extends关键字

Js ES6中的”Super“和”Extends以及原文链接https://medium.com/@anurag.majumdar

fetch与传统Ajax

首先了解到Ajax的本质是使用XMLHttpRequest对象来请求数据,fetch则是全局window的一个方法,而且它使用ES6 Promise来处理回调和结果,所以也需要浏览器支持ES6。

Fetch和Ajax主要有两种方式的不同;其中第一个是从Fetch返回的 Promise不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500,相反,它会正常解决 (其中ok状态设置为false),。仅在网络故障时或任何阻止请求完成时,它才会拒绝。如果有需求一般都可以自行封装。

第二个是Fetch在默认情况下在服务端不会发送或接收任何cookie,如果站点依赖于维护一个用户会话,则可能导致未经认证的请求。如果需要可以在参数中加上credentials

fetch(url, {
  credentials: same-origin', // credentials: ’include'
})

Promise封装Ajax请求

let ajax = (obj) => {
    return new Promise((resolve, reject) => {
        let method = obj.method || 'GET';
        let xhr = null;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else {
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        xhr.onReadyStateChange = () => {
            if (xhr.readyState == 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                    resolve(xhr.responseText);
                } else {
                    reject(xhr.statusText);
                }
            }
        }
        if (method == 'POST') {
            xhr.open('POST', obj.url, true);
            xhr.responseType = "json";
            xhr.setRequestHeader("Accept", "application/json");
            xhr.send(obj.data);
        } else {
            let query = '';
            for (let key in obj.data) {
                query += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(obj.data[key]);
            }
            query.substring(1);
            xhr.open('GET', obj.url + '?' + query, true);
            xhr.send();
        }
    })
}

计算机相关

Linux常用命令

  • ps -aux:查看执行中的程序的一个指令
  • kill:用来杀进程的,需配合 ps 这个指令,如kill 100
  • netstat -tln:查看端口使用情况
  • netstat -tln | grep 8083 :只查看端口8083的使用情况
Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.