# 前端常见的面试题(一)

# JavaScript规定了几种语言类型

7,6种基本类型,1种引用类型,Number,String,Boolean,Object,Undeifned,Null,Smbol(ES6)

# JavaScript 对象的底层数据结构是什么

线性类型:数组,链表,队列(First In First Out),(First In Last Out)
非线性:树,图
参考:http://www.alloyteam.com/2015/09/brief-javascript-data-structures-and-algorithms-the-array/

# Symbol在实际开发中的

// Symobl 是ES6中提出来的,为了解决对象属性字段名重名冲突的问题,表示独一无二的值
// 参考 http://es6.ruanyifeng.com/#docs/symbol 
// 参考 https://segmentfault.com/a/1190000015262174
(function () {
    var root = this;
    var SymbolPolyfill = function Symbol(description) {
        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description);

        var symbol = Object.create(null);

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            }
        });
        return symbol;
    }
    root.SymbolPolyfill = SymbolPolyfill;
})();

# JavaScript 中的变量在内存中的存储形式

堆,栈,堆栈,队列的概念
堆:是一种经过排序的树形数据结构,堆发生在程序运行是,而不是再编译时,可以随心访问数据,堆有2个性质
1:堆总是一颗完全二叉树
2:堆中某个节点的值,总是不大于或不小于其父节点的值
栈:先进后出
队列:先进先出

# 基本类型对应的内置对象

string --> String
boolean ---> Boolean
number ----> Number
symbol ----> Symbol

# 可能发生隐式类型转换的场景以及转换规则,应如何避免或巧妙应用

数字与字符串+-号操作,.点符号运算的时候可能会当成对象,if条件判断表达式会隐式转为Boolean类型

# 0.1 + 0.2 == 0.3; 为什么输出 false

JavaScript 出现小数精度丢失的原因 JavaScript 采用IEEE754规范,计算机而矜持存储十进制的小数时不能完整的表示小数,那如何判断0.1+0.2 === 0.3 呢: 常见的做法是设置一个误差范围值,通常称为"机器精度",对于JavaScript的数字来说,该值通常是 2^-52 (2.220446049250313e-16),从ES6开始,该值定义在 Number.EPSILON 中,我们可以直接调用,也可以为ES6之前的版本写polyfill

if(!Number.EPSILON){
    Number.EPSILON =  Math.pow(2,-52);
}

可以使用 Number.EPSILON 来比较两个数字是否相等(在指定的误差范围内):

function numbersCloseEnoughToEqual(n1,n2) {
 return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false

# 原型和原型链

[原型规则]:
1:所有的引用类型(数组,对象,函数)都具有扩展属性的特性
2:偶有的引用类型(数组,对象,函数)都有一个__proto__(隐式原型)对象
3:所有的函数都有一个prototype显示原型,属性值也是一个普通对象
4:所有的引用类型(数组,对象,函数)的隐式原型都指向其构造函数的显示原型,即:obj.__proto__ === Object.protoptye
5:函数的prototype属性的构造函数指向函数的本身
6:原型链的最顶端 Object.prototype.__proto__ === null

原型和原型链

# instanceof 的底层实现原理

function instanceOf(L,R){
    var proto_l = L.__proto__;
    var proto_r = R.prototype;
    while(true){
        if(proto_l === null){
            return false;
        } else if(proto_l === proto_r){
            return true;
        }
        proto_l = proto_l.__proto__;
    }
    return false;
}

# 实现继承的几种方式以及他们的优缺点

1:原型链prototype继承,原型链继承通过 Sub.prototype = new Parent,构造函数原型上的熟悉在构造函数的实例上是共享的,即没有实现私有化
2:构造函数继承  
function Super(){}
function Sub(){Super.call(this)} 
实现了属性的私有话,但是子类无法方位父类原型上的属性

# 描述 new 一个对象的详细过程,并手动实现一个new 方法

1:创建一个空对象
2:获取构造函数
3:链接到原型
4:绑定this
5:返回一个空对象
参考:https://www.jianshu.com/p/9cee6a703e01
function create(){
    let obj = new Object();
    let Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    let result = Constructor.apply(obj,arguments);
    return typeof result === "object" ? result : obj;
}
function People(name,age){
    this.name = name;
    this.age = age;
}
create(People,'Rose',10);

# 理解ES6 Class 构造函数以及继承的底层实现原理

class Cart extends{
    constructor(){
        super();
    }
}

# 实现一个函数,使其输出: mul(2)(3)(4)

var currying = function(fn){
    var _args = [].slice.call(arguments,1);
    let rec = function(){
        if(arguments.length != 0){
            _args = _args.concat([].slice.call(arguments))
            return rec;
        }
    }
    rec.toString = function(){
        if(typeof fn === "function"){
            return fn.apply(null,_args);
        }
    }
    return rec;
}

var sum = currying(function(){
    var args = [].slice.call(arguments);
    return args.reduce(function(a,b){
        return a+b;
    });
});

console.log(sum(20, 10)(5,5)(5));

# 递归测试

function total(x){
    if(x === 1){
        return 1;
    }
    var result = x + total(x - 1);
    return result;
}
console.log(total(4));

# 什么是事件代理

/**
* 事件代理是利用事件冒泡特性,将事件绑定到父元素上面,利用event,target判* 断是否等于目标元素,从而进行事件执行,具有以下优点
* 1:减少监听器
* 2:可以实现动态监听
**/
var ul = document.getElementById("list");
ul.addEventListener("click",function(e){
    var l = e.target;
    while(l.tagName.toUpperCase() !== "LI"){
        l = l.parentNode;
        if(l === ul){
            l = null;
            break;
        }
    }
    if(l){
        console.log("点击了ul的li");
    }
});

# 箭头函数和普通函数的区别

1:箭头函数不能被new 实例创建
2: 箭头函数没有prototype 对象
3:箭头函数参数没有arguments对象
4:箭头函数this 指向外层函数的上下文this 对象

# 正则实现对数字每3位增加逗号

// API 版本
(123456789.987654).toLocalString('en-US');

/**
* 正则方式
* 1:位置开头不能是逗号,  (?!^)
* 2:从后面算起每3个数字的前面位置新增逗号(?=(\d{3})+$)
* 3:将规则拼接起来
*
**/

"123456789.987654".replace(/(?!\b)(?=(\d{3})+\b)/g,",");

# js 正则查找排除某个字符

// 排除2
var reg = /.[^a]*/g

# Webpack 和 gulp,grunt 区别

1:grunt和gulp 基于任务流的task任务打包
2:webpack基于模块工具Bunlder

# webpack 打包过程(主要围绕模块进行讲解)

1: 读取文件,分析模块的依赖配置
2: 对依赖的模块进行解析,深度遍历
3:将解析的模块,使用不同的loader 进行处理
4:编译模块,生成抽象语法树ABT
5:遍历ABT,输出js 文件

# webpack打包优化

1:体积较大的文件使用webpack-bundle-analyzer插件 在plugins:[new BundleAnalyzerPlugin()]添加即可
2:第三方js 或者css 使用CDN 外链
3:压缩混淆代码,服务器开启gzip工具压缩
4:模块化的引入,如:
import {chain, cloneDeep} from 'lodash';
// 可以改写为
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';

# 谈谈你对Vuex的理解

Vuex包含的5个重要属性,state,getter,mutation,action,module
1:state:负责存储数据,存储状态,当注册store实例后,可以通过,this.$store.xx来访问数据,且数据为响应式
2:getter:修改state属性的方法,存放组件一些公共方法,它的返回值会被依赖缓存起来,当依赖值发生改变时候会重新计算
3:mutation 更改Vuex中store状态的唯一方式是提交mutation,同步代码
4:action 包含任意一部的操作,通过提交mutation间接更改状态,组件可以通过method dispatch出发
5:moudle 将store 分割成模块,每个模块都有对应的getter,mutation,action,state,将moudle注册到Vuex实例上

# Vue声明周期

beforeCreate -> created -> beforeMount -> mounted -> beforeUpdate -> updated -> beforeDestory -> destoryed

# Vue DOM渲染的过程和原理

1:new Vue 初始化Vue实例
2:通过三种渲染模式Render、el、template生成Render 函数
3:通过Watcher监听数据变化
4:当数据变化时候,通过Render生成VNode
5;通过patchVNode对比前后变化,通过diff进行更新,添加,删除等操作生成真实的DOM节点

# http三次握手

1:客户端发送SYN报文标识给服务端
2:服务端接收,返回SYN+ACK报文给客户端
3:客户端发送带ACK报文标识给服务端

# 简单介绍一下MVVM模式

1:new MVVM() 初始化实例
2:Observer   通过DefineProperty 劫持属性
3:Compile    初始化模板指令
4:Dep        通过Observer劫持的属性发布订阅,通知相关事件
5:Watcher    监听事件
6:Update     根据Watcher 监听的数据更新模板

# Vue中组件的通信方式

1:父组件通过props向子组件传递数据,用一个形象的比喻来说,父子组件之间的数据相当于自上而下的水管子,只能从上往下流,不能逆流,这也正是Vue的设计理念:单项数据流
2:$emit: 子组件触发消息传递给父组件回调函数绑定执行
3:父组件调用子组件的方法:this.$refs.子组件名称.方法名
4:子组件调用父组件方法: this.$parent.方法名
5:同级之间的通信使用vuex进行管理
6:使用事件总线 EventBus
7:使用$bordcast和$dispatch 事件

# 说说你项目中遇到的难点

1:PWA接入,学习相关技术文档,项目实践
2:多属性商品切换
3:交互体验
4:购物车交互逻辑
5:网站性能优化
6:canvas 雪花效果

# 浏览器的渲染过程

1:http请求加载资源
2: parse 解析HTML,css
3:构建render tree
4:compute style,合成
5: layout 布局,repaint重绘,reflow回流
6:呈现页面

# v-for循环时为什么要加key

vue虚拟DOM中,数据发生变化时候,需要找到对应的DOM进行更新,新增key可以给个唯一标识,为了高效的更新虚拟DOM

# 客路旅行web前端面试题

请根据以下例子,实现异步的compose 函数 compose(...functions)

/**
 * 
 * 客路旅行web前端面试题压轴题,请根据以下例子,实现异步的compose 函数  compose(...functions)
 * 
 *  Example ---------------------------
 *  
 *  function add1(n,callback){
        setTimeout(function(){
            callback(null,n+1)
        },10);
    }

    function mul3(n,callback){
        setTimeout(function(){
            callback(null,n*3);
        },10);
    }
    
    var add1mul3 = compose(mul3,add1);
    add1mul3(4,function(err,result){
        // result now equals 15
    })
 *  
 */


function add1(n,callback){
    setTimeout(function(){
        callback(null,n+1)
    },10);
}

function mul3(n,callback){
    setTimeout(function(){
        callback(null,n*3);
    },10);
}

 

function compose(){
    let argsFn = [].slice.apply(arguments);
    let promiseArr = argsFn.map( fn => {
        return function(n){
            return new Promise((resolve,reject) => {
                fn(n,function(obj,number){
                    resolve(number);
                });
            });
        }
    });

    function excutor(arr,value){
        return new Promise((resolve,reject) => {
            try {
                (function rec(a,v) {
                    if(a.length === 0){
                        return resolve(v);
                    }
                    let fn = a.pop();
                    fn(v).then(function(num){
                        rec(a,num);
                    });
                })(arr,value);
            } catch (error) {
                return resolve(error);
            }
        });
    }

    return function(x,cb){
        excutor(promiseArr,x).then(function(result){
            if(result instanceof Error){
                cb(result,undefined);
            }else {
                cb(null,result);
            }
        });
    }
}
 
var add1mul3 = compose(mul3,add1);
add1mul3(4,function(err,result){
    console.log(result);
})

# 实现以下算法将数组截取

/**
* 原数组['a','b','c','d'] 
* 写一个方法
* fn(['a','b','c','d'],2) 输出结果[['a','b'],['c','d']]
* fn(['a','b','c','d'],3) 输出结果[['a','b','c'],['d']]
*
**/
function fn(arr,length){
    var result = [];
    (function rec(a,len){
        if(a.length <= len){
            result.push(a);
            return;
        }
        let temp = a.splice(0,len);
        result.push(temp);
        rec(a,len);
    })(arr,length);
    return result;
}

# 自定义实现call

let Person = {
    name: 'Tom',
    say() {
        console.log(`我叫${this.name}`)
    }
}
let Person2 = {
    name:"qr"
}
window.name = "Bob";

console.log(Person.say());
console.log(Person.say.call());


Function.prototype.mycall = function(){
    let args = [...arguments];
    let ctx = args.splice(0,1)[0];
    let otherArgs = args.splice(0);
    ctx = ctx || window;
    ctx.__fn__ = this;
    let result = ctx.__fn__(...otherArgs);
    delete ctx.__fn__;
    return result;
}

console.log(Person.say.mycall(Person2));
var timeCallback = function(x,y,z){
    console.log("set timeout mycall==",this.name,x,y,z);
}   
setTimeout(function(){
    timeCallback.mycall(Person,"x","y","z");
},1000);


var callback = function(){
    console.log(this);
}
setTimeout(callback.bind(Person),3000);

# 双飞燕布局

<style>
* {
    margin: 0;
    padding: 0;
}

#left {
    float: left;
    /*只设置浮动,不设宽度*/
    height: 500px;
    width: 100px;
    background-color: #f00;

    margin-left: -100%;  
}

#right {
    overflow: hidden;
    /*触发bfc*/
    height: 500px;
    width: 100px; margin-left: -100px;  
    float: right;
    background-color: #0f0;
}

#center {
    height: 500px;
    background: gray;
    float: left;
    width: 100%; 
}

#parent {
    overflow: hidden;
    position: relative;
    
}
#center-inner{ padding-left: 100px; padding-right: 100px;}
</style>
<div id="parent">
    <div id="center">
        <div id="center-inner">双飞燕布局 中间内容自适应</div>
    </div>
    <div id="left">左列不定宽</div>
    <div id="right">右列自适应</div>
</div>

# 圣杯布局

<style>
    *{
        margin: 0;
        padding: 0;
    }
    
    #parent{
        position: relative; overflow: hidden;
        padding-left: 100px; padding-right: 100px;
    }
    #center{
        float: left; position: relative;
        height: 500px; background: gray; width: 100%;
    }

    
    #left{
        float: left; width: 100px; height: 500px; 
        background: red; left: -100px;
        position: relative; margin-left: -100%;  
    }

    #right{
        float: right; width: 100px; height: 500px; 
        background: green;  position: relative;
        margin-left: -100px; right: -100px;
    }
</style>
<div id="parent">
    <div id="center"> 中间内容自适应  </div>
    <div id="left">左列不定宽</div>
    <div id="right">右列自适应</div>
</div>

# 高性能渲染10万条数据

<div id="container"></div>
<script>
/**
 *  requestAnimationFrame 可以根据性能渲染执行,
 *  不要使用setTimeout,
 * 
 * */

    //需要插入的容器
    let ul = document.getElementById('container');
    // 插入十万条数据
    let total = 100000;
    // 一次插入 20 条
    let once = 20;
    //总页数
    let page = total / once
    //每条记录的索引
    let index = 0;
    //循环加载数据
    function loop(curTotal, curIndex) {
        if (curTotal <= 0) {
            return false;
        }
        //每页多少条
        let pageCount = Math.min(curTotal, once);
        window.requestAnimationFrame(function () {
            let fragment = document.createDocumentFragment();
            for (let i = 0; i < pageCount; i++) {
                let li = document.createElement('li');
                li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
                fragment.appendChild(li)
            }
            ul.appendChild(fragment)
            loop(curTotal - pageCount, curIndex + pageCount)
        })
    }
    loop(total, index);
</script>

# Vue 项目后端权限如何控制

1: router 基础配置 404,login 不需要权限的页面
2:通过router.beforeEach() 钩子函数拦截路由
3: 判断登录用户的类型,添加 mate,role 属性判断
4:通过router.addRouters 方法动态注册该用户的router 权限信息

# Vue 项目中keep-alive作用是什么

1:包含在 <keep-alive :is="currentView"> 中创建的组件,会多出2个生命周期的钩子函数,activated与deactivated
2: activated:组件被激活时调用,组件第一次渲染的时候也会被调用,之后每次激活时候也会被调用
3:deactivated:组件停用时候会调用
4:keep-alive 通常配合router-view使用,会将整个路由页面一切缓存下来
5:2.1.0版本以后新增 include 属性允许组件有条件的缓存,支持逗号分隔,正则匹配,或者数组表示
<!-- template -->
<keep-alive>
    <router-view v-if="$router.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$router.meta.keepAlive"></router-view>
<script>
    new Router({
        routes: [
            {
                name: 'a',
                path: '/a',
                component: A,
                meta: {
                    keepAlive: true
                }
            },
            {
                name: 'b',
                path: '/b',
                component: B
            }
        ]
    })
</script>

# 输出以下代码的执行结果

async function test(){
    console.log(1);
    
    await test2();

    console.log(300);

    let x = await 200;
    console.log(x);
}

async function test2(){
    console.log(400);
}

console.log(0);
test();
console.log(2);

// 输出结果为  0  1  400  2  300  200
// test 执行结果返回一个Promise 对象resolve 状态
// x 的执行结果返回的是一个具体的值
// async 语法声明的函数为宏任务函数,
// await 语句会阻塞下面语句的执行

# 手动实现js 数组 Map 方法

function myMap(arr,fn){
    let result = [];
    for(let i = 0; i < arr.length; i++){
        let x = fn.call(arr,arr[i],i);
        result.push(x);
    }
    return result;
}
var a = [1,2,3,4];
var b = myMap(a,function(item){
    return item + 2;
})
console.log(b);

# Vuejs Hooks