2019前端基础面试秘籍(更新于5.13)

77777777777777777777777777777777777777777777777777777777777777777777777777

image

一、html和css部分

1、如何理解CSS的盒子模型?

标准盒子模型:宽度=内容的宽度(content)+ border + padding

低版本IE盒子模型:宽度=内容宽度(content+border+padding)

2、BFC?

* 什么是 BFC

   BFC(Block Formatting Context)格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
* 形成 BFC 的条件

   * 浮动元素,float 除 none 以外的值
   * 定位元素,position(absolute,fixed)
   * display 为以下其中之一的值 inline-block,table-cell,table-caption
   * overflow 除了 visible 以外的值(hidden,auto,scroll)
* BFC 的特性
   * 内部的 Box 会在垂直方向上一个接一个的放置。
   * 垂直方向上的距离由 margin 决定
   * bfc 的区域不会与 float 的元素区域重叠。
   * 计算 bfc 的高度时,浮动元素也参与计算
   * bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。

3、如何清除浮动?

不清楚浮动会发生高度塌陷:浮动元素父元素高度自适应(父元素不写高度时,子元素写了浮动后,父元素会发生高度塌陷)
* clear清除浮动(添加空div法)在浮动元素下方添加空div,并给该元素写css样式:   {clear:both;height:0;overflow:hidden;}
* 给浮动元素父级设置高度
* 父级同时浮动(需要给父级同级元素添加浮动)
* 父级设置成inline-block,其margin: 0 auto居中方式失效
* 给父级添加overflow:hidden 清除浮动方法
* 万能清除法 after伪类 清浮动(现在主流方法,推荐使用)

.float_div:after{
    content:".";
    clear:both;
    display:block;
    height:0;
    overflow:hidden;
    visibility:hidden;
}
.float_div{
    zoom:1
} 

4、用纯CSS创建一个三角形的原理是什么?

span {
    width: 0;
    height: 0;
    border-top: 40px solid transparent;
    border-left: 40px solid transparent;
    border-right: 40px solid transparent;
    border-bottom: 40px solid #ff0000;
}
image

5、css3实现0.5px的细线?

/* css */
.line {
    position: relative;
}
.line:after {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 1px;
    background-color: #000000;
    -webkit-transform: scaleY(.5);
    transform: scaleY(.5);
}

/* html */

6、css实现三栏布局

左右固定,中间自适应。

image
  1. flex方式



    
    
    
    Document
    


    
  1. 绝对定位方式



    
    
    
    Document
    


    
  1. 浮动方式



    
    
    
    Document
    


    

7、让一个div垂直居中

image
  1. 宽度和高度已知的



    
    
    
    Document
    


    
  1. 宽度和高度未知



    
    
    
    Document
    


    
  1. flex布局



    
    
    
    Document
    


    

二、JS

1、闭包

闭包概念

能够读取其他函数内部变量的函数。
或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。

闭包用途

1、读取函数内部的变量
2、让这些变量的值始终保持在内存中。不会再f1调用后被自动清除。
3、方便调用上下文的局部变量。利于代码封装。
原因:f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖f1,因此f1也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。

闭包缺点

1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包应用场景

闭包应用场景之setTimeout

//setTimeout传递的第一个函数不能带参数
setTimeout((param) => {
    alert(param)
}, 1000);


//通过闭包可以实现传参效果
function func(param) {
    return function() {
        alert(param)
    }
}
var f1 = func('汪某');
setTimeout(f1, 1000)//汪某

2、js中函数执行

在 ES5.1 里面函数是这样执行的(不讨论use strict和一些特殊情况,JS好复杂的),按如下顺序执行:

1. 确定“this”的值 (确切的来说,this在JS里面不是一个变量名而是一个关键字)
2. 创建一个新的作用域
3. 处理形参/实参(没有定义过才声明,无论如何都重新赋值,没有对应实参则赋值为"undefined"): 
对于每一个传入的实参,按照从左往右的顺序依次执行:如果对应的形参在本作用域中还没有定义,则在本作用域中声明形参,并赋值。如果已经定义过了,则重新给其赋值。(没有对应实参则赋值为"undefined")(没有定义:就是“没有声明”的意思)
4. 处理函数定义(没有定义过才声明,无论如何都重新赋值): 
对该函数中所有的定义的函数,按照代码写的顺序依次执行:如果这个变量名在本作用域中还没有定义,则在本作用域中声明这个函数名,并且赋值为对应的函数,如果定义了这个变量,在可写的情况下重新给这个变量赋值为这个函数,否则抛出异常。
5. 处理 "arguments"(没有定义过才声明和赋值): 
如果在本作用域中没有定义 arguments,则在本作用域中声明arguments并给其赋值。
6. 处理变量声明(没有定义过才声明,不赋值):
对于所有变量声明,按照代码写的顺序依次执行:如果在本作用域中没有定义这个变量,则在本作用域中声明这个变量,赋值为undefined
7. 然后执行函数代码。(当然是去变量定义里面的 var 执行)

3、new一个对象的过程中发生了什么嘛

1. 创建空对象;
var obj = {};
2. 设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象;
obj.__proto__ = ClassA.prototype;
3. 使用新对象调用函数,函数中的this被指向新实例对象:
ClassA.call(obj);//{}.构造函数();          
4. 如果无返回值或者返回一个非对象值,则将新对象返回;如果返回值是一个新对象的话那么直接直接返回该对象。

4、宏任务跟微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

这一次,彻底弄懂 JavaScript 执行机制

5、防抖和节流

综合应用场景

  • 防抖(debounce):就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
    • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • 节流(throttle):就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
    • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
      所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

防抖函数分为非立即执行版和立即执行版。

  • 非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
  • 立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func,wait,immediate) {
    let timeout;

    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率。

对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。

  • 时间戳版的函数触发是在时间段内开始的时候
  • 定时器版的函数触发是在时间段内结束的时候。
/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        var previous = 0;
    }else if(type===2){
        var timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }
    }
}

6、数组的常用方法

改变原数组的方法

  • splice() 添加/删除数组元素
语法:arrayObject.splice(index,howmany,item1,.....,itemX)
参数:
   1.index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
   2.howmany:可选。要删除的项目数量。如果设置为 0,则不会删除项目。
   3.item1, ..., itemX: 可选。向数组添加的新项目。

返回值: 如果有元素被删除,返回包含被删除项目的新数组。
  • sort() 数组排序
语法:arrayObject.sort(sortby)
参数:
   1.sortby 可选。规定排序顺序。必须是函数。。

返回值: 返回包排序后的新数组。
  • pop() 删除一个数组中的最后的一个元素
语法:arrayObject.pop()
参数:无

返回值: 返回被删除的元素。
  • shift() 删除数组的第一个元素
语法:arrayObject.shift()
参数:无

返回值: 返回被删除的元素。
  • push() 向数组的末尾添加元素
语法:arrayObject.push(newelement1,newelement2,....,newelementX)
参数:
   1.newelement1    必需。要添加到数组的第一个元素。
   2.newelement2    可选。要添加到数组的第二个元素。
   3.newelementX    可选。可添加若干个元素。

返回值: arrayObject 的新长度。
  • unshift() 向数组的开头添加一个或更多元素
语法:arrayObject.unshift(newelement1,newelement2,....,newelementX)
参数:
   1.newelement1    必需。要添加到数组的第一个元素。
   2.newelement2    可选。要添加到数组的第二个元素。
   3.newelementX    可选。可添加若干个元素。

返回值: arrayObject 的新长度。
  • reverse() 颠倒数组中元素的顺序
语法:arrayObject.reverse()
参数:无

返回值: 颠倒后的新数组。
  • copyWithin() 指定位置的成员复制到其他位置
语法: array.copyWithin(target, start = 0, end = this.length)
参数:
   1.target(必需):从该位置开始替换数据。如果为负值,表示倒数。
   2.start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
   3.end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

返回值: 返回当前数组。
  • fill() 填充数组
语法: array.fill(value, start, end)
参数:
   1.value  必需。填充的值。
   2.start  可选。开始填充位置。
   3.end    可选。停止填充位置 (默认为 array.length)

返回值: 返回当前数组。

不改变原数组的方法

  • slice() 浅拷贝数组的元素
语法: array.slice(begin, end);
参数:
   1.begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。
   2.end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。

返回值: 返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。
  • join() 数组转字符串
语法:array.join(str)
参数:
   1.str(可选): 指定要使用的分隔符,默认使用逗号作为分隔符。

返回值: 返回生成的字符串。
  • concat() 合并两个或多个数组
语法: var newArr =oldArray.concat(arrayX,arrayX,......,arrayX)
参数:
   1.arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个。

返回值: 返回返回合并后的新数组。
  • indexOf() 查找数组是否存在某个元素
语法:array.indexOf(searchElement,fromIndex)
参数:
   1.searchElement(必须):被查找的元素
   2.fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。

返回值: 返回下标
  • lastIndexOf() 查找指定元素在数组中的最后一个位置
语法:arr.lastIndexOf(searchElement,fromIndex)
参数:
   1.searchElement(必须): 被查找的元素
   2.fromIndex(可选): 逆向查找开始位置,默认值数组的长度-1,即查找整个数组。

返回值: 方法返回指定元素,在数组中的最后一个的索引,如果不存在则返回 -1。(从数组后面往前查找)
  • includes() 查找数组是否包含某个元素
语法: array.includes(searchElement,fromIndex=0)
参数:
   1.searchElement(必须):被查找的元素
   2.fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。

返回值: 返回布尔

7、立即执行函数

声明一个匿名函数,马上调用这个匿名函数。目的是保护内部变量不受污染。

(function(n1, n2) {
    console.log("这是匿名函数的自执行的第一种写法,结果为:" + (n1 + n2))
})(10, 100);
(function start(n1, n2) {
    console.log("这是函数声明方式的自执行的第一种写法,结果为:" + (n1 + n2))
})(10, 100);
(function(n1, n2) {
    console.log("这是匿名函数的自执行的第二种写法,结果为:" + (n1 + n2))
}(10, 100));
(function start(n1, n2) {
    console.log("这是函数声明方式的自执行的第二种写法,结果为:" + (n1 + n2))
}(10, 100));

8、js原型和原型链

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。

关系:instance.constructor.prototype = instance.proto

特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本,当我们修改原型时,与之相关的对象也会继承这一改变。
当我们需要一个属性时,JavaScript引擎会先看当前对象中是否有这个属性,如果没有的话,就会查找它的prototype对象是否有这个属性,如此递推下去,一致检索到Object内建对象。


function Func(){}
Func.prototype.name = "汪某";
Func.prototype.getInfo = function() {
   return this.name;
}
var person = new Func();
console.log(person.getInfo());//"汪某"
console.log(Func.prototype);//Func { name = "汪某", getInfo = function() }

参考:js原型和原型链

9、js中call,apply,bind

参考:JavaScript中call,apply,bind方法的总结。

10、Promise

一句话概括Promise:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。

promise是用来解决两个问题的:

  • 回调地狱,代码难以维护,常常第一个的函数的输出是第二个函数的输入这种现象
  • promise可以支持多个并发的请求,获取并发请求中的数据

这个promise可以解决异步的问题,本身不能说promise是异步的

/*Promise 的简单实现*/

class MyPromise {
    constructor(fn) {
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
        this.state = "PADDING";
        this.value = "";
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(value) {
        if (this.state === "PADDING") {
            this.state = "RESOLVED";
            this.value = value;
            this.resolvedCallbacks.forEach(cb => cb());
        }
    }
    reject(value) {
        if (this.state === "PADDING") {
            this.state = "REJECTED";
            this.value = value;
            this.rejectedCallbacks.forEach(cb => cb());
        }
    }
    then(resolve = function() {}, reject = function() {}) {
        if (this.state === "PADDING") {
            this.resolvedCallbacks.push(resolve);
            this.rejectedCallbacks.push(reject);
        }
        if (this.state === "RESOLVED") {
            resolve(this.value);
        }
        if (this.state === "REJECTED") {
            reject(this.value);
        }
    }
}

11、async/await

如何使用 Async 函数

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

上面代码指定50毫秒以后,输出hello world。 进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

待补充。。。

12、深拷贝、浅拷贝

浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;

区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制;

image

浅拷贝的实现方式

  1. 自定义函数
function simpleCopy (initalObj) {
   var obj = {};
   for ( var i in initalObj) {
    obj[i] = initalObj[i];
   }
   return obj;
}
  1. ES6 的 Object.assign()
let newObj = Object.assign({}, obj);
  1. ES6 的对象扩展
let newObj = {...obj};

深拷贝的实现方式

  1. JSON.stringify 和 JSON.parse

用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象。

let newObj = JSON.parse(JSON.stringify(obj));
  1. lodash

用 lodash 函数库提供的 _.cloneDeep 方法实现深拷贝。

var _ = require('lodash');
var newObj = _.cloneDeep(obj);
  1. 自己封装
function deepClone(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj === "object") {
        // for...in 会把继承的属性一起遍历
        for (let key in obj) {
            // 判断是不是自有属性,而不是继承属性
            if (obj.hasOwnProperty(key)) {
                //判断ojb子元素是否为对象或数组,如果是,递归复制
                if (obj[key] && typeof obj[key] === "object") {
                    objClone[key] = this.deepClone(obj[key]);
                } else {
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}

13、跨域

跨域需要针对浏览器的同源策略来理解,同源策略指的是请求必须是同一个端口,同一个协议,同一个域名,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。

受浏览器同源策略的影响,不是同源的脚本不能操作其他源下面的对象。想要操作另一个源下的对象是就需要跨域。

  • jsonp
  • iframe
  • 跨域资源共享(CORS)
  • nginx 代理跨域

14、for in 和 for of

  • for in
1.一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行。
2.不建议使用for in 遍历数组,因为输出的顺序是不固定的。
3.如果迭代的对象的变量值是null或者undefined, for in不执行循环体,建议在使用for in循环之前,先检查该对象的值是不是null或者undefined
  • for of
1.for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

遍历对象

var s = {
    a: 1,
    b: 2,
    c: 3
}
var s1 = Object.create(s);
for (var prop in s1) {
    console.log(prop); //a b c
    console.log(s1[prop]); //1 2 3
}
for (let prop of s1) {
    console.log(prop); //报错如下 Uncaught TypeError: s1 is not iterable 
}
for (let prop of Object.keys(s1)) {
    console.log(prop); // a b c
    console.log(s1[prop]); //1 2 3
}

15、如何阻止冒泡?

冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。

//阻止冒泡行为 
function stopBubble(e) { 
//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //因此它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //否则,我们需要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}

16、如何阻止默认事件?

w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

//阻止浏览器的默认行为 
function stopDefault( e ) { 
    //阻止默认浏览器动作(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函数器默认动作的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}

17、var,let,const

//变量提升
console.log(a);  // undefined
console.log(b);  // 报错
console.log(c);  // 报错
var a = 1;
let b = 2;
const c = 3;

// 全局声明
console.log(window.a) //  1 

// 重复声明
let b  = 200;//报错

其实这里很容易理解,var是可以变量提升的。而let和const是必须声明后才能调用的。 对于let和const来说,这里就是暂缓性死区。


image

18、Class

es6新增的Class其实也是语法糖,js底层其实没有class的概念的,其实也是原型继承的封装。

class People {
    constructor(props) {
        this.props = props;
        this.name = '汪某';
    }
    callMyName() {
        console.log(this.name);
    }
}
class Name extends People { // extends 其实就是继承了哪个类
    constructor(props) {
        //  super相当于 把类的原型拿过来 
        //  People.call(this, props)
        super(props)
    }
    callMyApple() {
        console.log('我是汪某!')
    }
}

let a = new Name('啊啊啊')
a.callMyName(); //汪某
a.callMyApple(); // 我是汪某!

19、Set

Set数据结构类似数组,但所有成员的值唯一。

let a = new Set();
[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));
for(let k of a){
    console.log(k)
};
// 1 2 3 4 5

基本使用

let a = new Set([1,2,3,3,4]);
[...a]; // [1,2,3,4]
a.size; // 4

// 数组去重
[...new Set([1,2,3,4,4,4])];// [1,2,3,4]

方法

  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。
let a = new Set();
a.add(1).add(2); // a => Set(2) {1, 2}
a.has(2);        // true
a.has(3);        // false
a.delete(2);     // true  a => Set(1) {1}
a.clear();       // a => Set(0) {}

20、Map

Map结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。

let a = new Map();
let b = {name: 'leo' };
a.set(b,'my name'); // 添加值
a.get(b);           // 获取值
a.size;      // 获取总数
a.has(b);    // 查询是否存在
a.delete(b); // 删除一个值
a.clear();   // 清空所有成员 无返回

基本使用

  • 传入数组作为参数,指定键值对的数组。
let a = new Map([
    ['name','wzx'],
    ['age',23]
])
  • 如果对同一个键多次赋值,后面的值将覆盖前面的值。
let a = new Map();
a.set(1,'aaa').set(1,'bbb');
a.get(1); // 'bbb'
  • 如果读取一个未知的键,则返回undefined。
new Map().get('asdsad'); // undefined
  • 同样的值的两个实例,在 Map 结构中被视为两个键。
let a = new Map();
let a1 = ['aaa'];
let a2 = ['aaa'];
a.set(a1,111).set(a2,222);
a.get(a1); // 111
a.get(a2); // 222

方法

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历 Map 的所有成员。
let a = new Map([
    ['name', 'leo'],
    ['age', 18]
])
for (let i of a.keys()) {
    console.log(i)
};
//name 
//age

for (let i of a.values()) {
    console.log(i)
};
//leo 
//18

for (let i of a.entries()) {
    console.log(i)
};
//["name", "leo"]

a.forEach((v, k, m) => {
    console.log(`key:${k},value:${v},map:${m}`)
})
//["age", 18]

三、手撸代码

1、实现一个new操作符

function New(func) {
    var res = {};
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return;
        ret;
    }
    return;
    res;
}
var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);

2、实现一个call或 apply

  • call
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}
  • apply
Function.prototype.apply2 = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

参考:JavaScript深入之call和apply的模拟实现

3、实现一个Function.bind

Function.prototype.bind2 = function (context) {
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};
    var fbound = function () {
        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }
    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();
    return fbound;
}

参考:JavaScript深入之bind的模拟实现

4、实现一个继承

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayName = function() {
    console.log('parent name:', this.name);
}

function Child(name, parentName) {
    Parent.call(this, parentName);
    this.name = name;
}

function create(proto) {
    function F() {}
    F.prototype = proto;
    return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
    console.log('child name:', this.name);
}

Child.prototype.constructor = Child;
var parent = new Parent('汪某');
parent.sayName();// parent name: 汪某
var child = new Child('son', '汪某');

5、手写一个Promise(中高级必考)

面试够用版

function myPromise(constructor) {
    let self = this;
    self.status = "pending"
        //定义状态改变前的初始状态
    self.value = undefined;
    //定义状态为resolved的时候的状态
    self.reason = undefined;
    //定义状态为rejected的时候的状态
    function resolve(value) {
        //两个==="pending",保证了状态的改变是不可逆的
        if (self.status === "pending") {
            self.value = value;
            self.status = "resolved";
        }
    }
    function reject(reason) {
        //两个==="pending",保证了状态的改变是不可逆的
        if (self.status === "pending") {
            self.reason = reason;
            self.status = "rejected";
        }
    }
    //捕获构造异常
    try {
        constructor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

//同时,需要在 myPromise的原型上定义链式调用的 then方法:
myPromise.prototype.then = function(onFullfilled, onRejected) {
    let self = this;
    switch (self.status) {
        case "resolved":
            onFullfilled(self.value);
            break;
        case "rejected":
            onRejected(self.reason);
            break;
        default:
    }
}

//测试一下:
var p = new myPromise(function(resolve, reject) {
    resolve(1)
});
p.then(function(x) {
    console.log(x)
})

高级版请参考:史上最最最详细的手写Promise教程

6、手写防抖(Debouncing)和节流(Throttling)

完整版详见上方,此处给出面试版

// 防抖函数
function debounce(fn, wait) {
    let timer;
    return function() {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, arguments)
        }, wait)
    }
}
// 节流函数
function throttle(fn, wait) {
    let prev = new Date();
    return function() {
        const args = arguments;
        const now = new Date();
        if (now - prev > wait) {
            fn.apply(this, args);
            prev = new Date();
        }
    }
}

7、手写一个JS深拷贝

面试版

function deepCopy(obj) {
    //判断是否是简单数据类型,
    if (typeof obj == "object") {
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for (let i in obj) {
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    } else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}

四、VUE

1、Vue2.0的双向数据绑定原理是什么?

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

//vue实现数据双向绑定的原理就是用Object.defineproperty()重新定义(set方法)对象设置属性值和(get方法)获取属性值的操纵来实现的。
//Object.property()方法的解释:Object.property(参数1,参数2,参数3)  返回值为该对象obj
//其中参数1为该对象(obj),参数2为要定义或修改的对象的属性名,参数3为属性描述符,属性描述符是一个对象,主要有两种形式:数据描述符和存取描述符。这两种对象只能选择一种使用,不能混合使用。而get和set属于存取描述符对象的属性。
//这个方法会直接在一个对象上定义一个新属性或者修改对象上的现有属性,并返回该对象。




    


    

Vue3.0将用原生Proxy替换Object.defineProperty

为什么要替换Object.defineProperty?

  • 在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
  • Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

什么是Proxy

  • Proxy是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。 Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
  • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
  • 使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。

2、请详细说下你对vue生命周期的理解?

总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后

  • beforeCreate 创建前执行(vue实例的挂载元素$el和数据对象data都为undefined,还未初始化)
  • created 完成创建 (完成了data数据初始化,el还未初始化)
  • beforeMount 载入前(vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。)
  • mounted 载入后html已经渲染(vue实例挂载完成,data.message成功渲染。)
  • beforeUpdate 更新前状态(view层的数据变化前,不是data中的数据改变前)
  • updated 更新状态后
  • beforeDestroy 销毁前
  • destroyed 销毁后 (在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在)

说一下每一个阶段可以做的事情

  • beforeCreate:可以在这里加一个loading事件,在加载实例时触发。
  • created:初始化完成时的事件写这里,如果这里结束了loading事件,异步请求也在这里调用。
  • mounted:挂在元素,获取到DOM节点
  • updated:对数据进行处理的函数写这里。
  • beforeDestroy:可以写一个确认停止事件的确认框。

附上一张中文解析图


image

3、动态路由定义和获取

在 router 目录下的 index.js 文件中,对 path 属性加上 /:id。

使用 router 对象的 params.id 获取

4、vue-router 有哪几种导航钩子?

三种

  1. 全局导航钩子(跳转前进行判断拦截)
    • router.beforeEach(to, from, next),
    • router.beforeResolve(to, from, next),
    • router.afterEach(to, from ,next)
  2. 组件内钩子
    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave
  3. 单独路由独享组件
    • beforeEnter

5、组件之间的传值通信?

  • 父组件向子组件传值:
    • 子组件在props中创建一个属性,用来接收父组件传过来的值;
    • 在父组件中注册子组件;
    • 在子组件标签中添加子组件props中创建的属性;
    • 把需要传给子组件的值赋给该属性
  • 子组件向父组件传值:
    • 子组件中需要以某种方式(如点击事件)的方法来触发一个自定义的事件;
    • 将需要传的值作为$emit的第二个参数,该值将作为实参传给响应事件的方法;
    • 在父组件中注册子组件并在子组件标签上绑定自定义事件的监听。

6、vuex

是一个能方便vue实例及其组件传输数据的插件 方便传输数据,作为公共存储数据的一个库

state: 状态中心

mutations: 更改状态,同步的

actions: 异步更改状态

getters: 获取状态

modules: 将state分成多个modules,便于管理

应用场景:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车。

网上找的一个通俗易懂的了解vuex的例子

公司有个仓库

1.State(公司的仓库)

2.Getter(只能取出物品,包装一下,不能改变物品任何属性)

3.Muitation(仓库管理员,只有他可以直接存储到仓库)

4.Action(公司的物料采购员,负责从外面买东西和接货, 要往仓库存东西,告诉仓库管理员要存什么)

非常要注意的地方:只要刷新或者退出浏览器,仓库清空。

7、Vue hash 路由和 history 路由的区别

hash模式url里面永远带着#号,我们在开发当中默认使用这个模式。那么什么时候要用history模式呢?如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url适合推广宣传。当然其功能也有区别,比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok啦。

router有两种模式:hash模式(默认)、history模式(需配置mode: 'history')
 | hash | history
-|-|-
url显示 | 有#,很Low | 无#,好看 |
回车刷新 | 可以加载到hash值对应页面 | 一般就是404掉了 |
支持版本 | 支持低版本浏览器和IE浏览器 | 支持低版本浏览器和IE浏览器 |

8、diff算法

参考:详解vue的diff算法

五、计算机

1、DNS 解析的详细过程

当我们在浏览器中输入一个URL,例如”www.google.com”时,这个地址并不是谷歌网站真正意义上的地址。互联网上每一台计算机的唯一标识是它的IP地址,因此我们输入的网址首先需要先解析为IP地址,这一过程叫做DNS解析。

DNS解析是一个递归查询的过程。例如,我们需要解析”www.google.com”时,会经历以下步骤:

  • 在本地域名服务器中查询IP地址,未找到域名;
  • 本地域名服务器回向根域名服务器发送请求,未找到域名;
  • 本地域名服务器向.com顶级域名服务器发送请求,未找到域名;
  • 本地域名服务器向.google.com域名服务器发送请求,找到该域名,将对应的IP返回给本地域名服务器。

2、简述三次握手

HTTP协议是使用TCP协议作为其传输层协议的,在拿到服务器的IP地址后,浏览器客户端会与服务器建立TCP连接。该过程包括三次握手:

  • 第一次握手:建立连接时,客户端向服务端发送请求报文
  • 第二次握手:服务器收到请求报文后,如同意连接,则向客户端发送确认报文
  • 第三次握手,客户端收到服务器的确认后,再次向服务器给出确认报文,完成连接。

三次握手主要是为了防止已经失效的请求报文字段发送给服务器,浪费资源。

3、四次挥手

客户端与服务器四次挥手,断开tcp连接。

  • 第一次挥手:客户端想分手,发送消息给服务器
  • 第二次挥手:服务器通知客户端已经接受到分手请求,但还没做好分手准备
  • 第三次回收:服务器已经做好分手准备,通知客户端
  • 第四次挥手:客户端发送消息给服务器,确定分手,服务器关闭连接

最后

以上为本小白个人总结,如有不对的地方,欢迎各位大佬在评论区指正,共勉!

你可能感兴趣的:(2019前端基础面试秘籍(更新于5.13))