中级前端面试题,不仅仅只要会使用,更要懂原理

2019-09-29  本文已影响0人  祝家庄打烊

什么事单线程?

主线程只有一个线程,同一时刻只能执行单个任务

为什么选择单线程?

JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。如:用户在同一个时刻,既要要变样式又要改变其宽度,这会程序不知所措

单线程意味着什么?

单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就需要一直等着。造成资源的浪费

如何解决单线程带来的性能问题?

异步处理可以解决单线程带来的性能问题,异步会重新开辟一个线程(辅助线程),加载执行异步任务,异步任务有了运行结果,会把任务扔到任务队列里,等待主线程所有的同步任务都执行完成后,才会执行任务队列里面的任务。

什么是事件循环机制?

所有同步任务都在主线程上的栈中执行 ——所有的异步任务有单独线程处理——处理结果存放到任务队列中(标记成事件)——栈中的同步任务都执行完,开始执行任务队列中的事件,循环往复的过程,就叫做事件循环机制。


Event Loop

this的原理?为什么会产生this?

简单点说,函数是存放在堆内存中,栈内存只存放着函数的引用地址,根据引用地址在堆内存中寻找执行的函数。堆内存的每个函数都是独立的,尽管对象中会有函数存在,也仅仅只是函数的引用。因为每个函数都是独立的,所以需要一个属性来指向当前的执行环境,this就因此产生。不懂得可以去看下阮一峰老师的文章,http://www.ruanyifeng.com/blog/2018/06/javascript-this.html

浏览器的执行时间线?

1.创建Document对象,标志着浏览器执行的开始,开始解析页面document.readyState="loading"
2.遇到link,创建线程,进行异步加载样式,并继续解析页面
3.遇到script外部加载js,两种情况,第一种没有设置async,defer属性,同步加载堵塞后面程序执行,等待脚本加载完成,才开始解析页面。第二种设置async,defer,重新创建线程加载,立马解析,不会等待。对于async属性的脚本,脚本加载完成后立即执行;对于defer属性的脚本,文档解析完成后,开始执行
4.遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档
5.当文档解析完成(domTree建立完毕,不是加载完毕),document.readyState=‘interative’
6.文档解析完成后,所有设置defer的脚本会按顺序执行(现实并不按顺序,js高程中有说明);
7.document对象触发DOMContentLoaded事件(jQuery入口函数其实就是触发的这个事件),这也标志着程序从同步脚本执行阶段,转化为事件驱动阶段。(也就标志着事件可以开始使用)
8.当所有异步的脚本加载完并执行后(img等页面所有的都加载并执行完后),document.readyState=‘complete’,window触发load事件;

new Function的实现原理?

先了解构造函数的内部实现,第一步:隐式的创建var this = {};第二步:隐式的return this。注意:new不是用来创建对象的,是用来继承的。

/*
    *new是用来继承的。怎么实现?
    *1.继承构造函数的属性
    *2.继承构成函数原型的属性
*/
function Zhuy(){
    this.name="朱家庄";
    this.content="努力成就未来";
    this.say=function(){
        console.log(this.name)
    }
    return {name:"zjz",z:"z"};   //注意:构造函数如果返回基本类型,默认返回this.如果返回的是对象,默认返回对象
}
Zhuy.prototype.order="钱多多"
       
function newObj(){
    var obj = {};
    constructor = Array.prototype.shift.call(arguments); //获取构造函数
    obj.__proto__=constructor.prototype; //返回的对象原型指向构造函数的原型
    var result = constructor.call(obj)    //执行构造函数,改变构造函数内的this指向
    return typeof(result)=="object"?result||obj:obj;
}
var person = newObj(Zhuy,"姓名","内容");
console.log("person",person);

Array.prototype.slice实现原理?

第一步:新创建数组对象。第二步:对this进行过滤,满足条件的存放到数组。第三步:返回数组对象。

/*
Array.prototype.slice.call(arguments)带有length属性的对象包括伪数组,转换成数组
Array.prototype.slice原理,对内部的this进行过滤,返回截取后的数组。
*/
var newArray = [1,2,3,4,5,6,7,8];
Array.prototype.sliceNew = function(){
    var arr = [];
    for(var i=0;i<this.length;i++){
        switch(arguments.length){
            case 0:
            arr.push(this[i]);
            break;
            case 1:
            i>=arguments[0]&&arr.push(this[i]);
            break;
            default:
            if(i>=arguments[0]&&i<arguments[1]){
                arr.push(this[i]);
            }
            break;
        }
    }
    return arr;
}
console.log(newArray.sliceNew(2,5));

原型的最终指向?

首先了解包装类的概念:原始值没有属性和方法,不能给原始值赋属性值,当给一个原始值赋予属性的时候,不会报错,会隐式的转化成对象(new Number或new String)。赋完值后,删除当前对象,以后程序也找到此属性和对象。


image

设计模式(23种),说出4种即可?

单例模式:一个类里面只能实例化一个对象,实例化构造函数,先判断当前实例是否存在,不存在的话,进行实例化并将值存储起来。
优势:防止多次实例化导致内存泄漏(每次实例化都会在堆中产生一个新的对象)

var mode1 = {
    Example:function(name,content){
        this.name = name;
        this.content = content;
    },
    single:function(name,content){
        if(!this.store){
            this.store=new this.Example(name,content);
        }
        console.log("this.store",this.store)
    }
}
mode1.single("祝家庄打样","666");

工厂模式:实例化一个对象,对象可以生产出各种属性和共有的方法,通过传递参数的形式来获取独特的方法。
优势:解耦.提供共有方法

function Factory(color,model){
    this.color=color;
    this.model=model;
    this.size=200;
    this.wheel=function(){
        console.log("制造轮子")
    };
    this.seat=function(){
        console.log("制造后背椅")
    };
    this.assemble=function(){
        console.log("进行组装")
    }
 }
var product = new Factory("黑色","BM");
product.wheel();
product.seat();
//所有工序都完成,可以组装车子了
product.assemble();

发布订阅模式:订阅者消息都存储起来,发布者遍历执行储存的消息
优势:发布者与订阅者耦合性降低,发布者不用去管订阅者如何去接收消息,只需要发送消息即可

var mode3={
    store:[],
    //订阅者,存储消息
    order:function(res,type){
        this.store.push({type:type,inform:res})
    },
    //发布者,遍历消息
    publish:function(){
        if(this.store.length==0) return false;
        var discount = null;
        for(var i=0;i<this.store.length;i++){
            switch (this.store[i].type){
                case "88":
                this.store[i].inform("发布一条消息:楼盘打折咯,"+this.store[i].type+"平方米九折优惠");
                break;
                case "100":
                this.store[i].inform("发布一条消息:楼盘打折咯,"+this.store[i].type+"平方米八折优惠");
                break;
                default:
                this.store[i].inform("发布一条消息:楼盘打折咯,"+this.store[i].type+"平方米七折优惠");
                        
            }
        }
    }
};
mode3.order(function(res){console.log(res)},"88")
mode3.order(function(res){console.log(res)},"100")
mode3.publish();

代理模式:类似一个中介,在客户端对象和提供服务者对象建立的一个桥梁
优势:解耦,客户端只需要通知代理对象即可

var Shop = function(name){
    this.name=name;
}
//商场买鞋店铺,8~18点店铺打样
Shop.prototype.buyShoes=function(){
    var hour = new Date().getHours();
    return (hour>8&&hour<18)?this.name:null
}
//助理去商场买鞋,并把消息返回给明星
var assistant = {
    buyShoes:function(res){
        start.buyShoes(res.buyShoes())
    }
}
//明星对象
var start={
    buyShoes:function(name){
        console.log("买了一双:"+name)
    }
}
//明星通知助理要买高跟鞋
assistant.buyShoes(new Shop("高跟鞋"))

策略模式:策略模式指的是定义一系列的算法,把它们一个个封装起来。
优势:解耦,每个算法都是独立的存在,不会相互影响
奖金算法(普通)

var calculateBouns = function(type,money){
    var defaultValue=money;
    switch(type){
        case "S":
        defaultValue=money*4;
        break;
        case "A":
        defaultValue=money*3;
        break;
        case "B":
        defaultValue=money*2;
        break;
      };
      return defaultValue;
}
console.log(calculateBouns("A",4000))

奖金算法(策略模式)

//设置每个策略
var PerformanceS = function(){};
PerformanceS.prototype.cal = function(salary){
    return salary*4;
}
var PerformanceA = function(){};
PerformanceA.prototype.cal = function(salary){
    return salary*3;
}
var PerformanceB = function(){};
PerformanceB.prototype.cal = function(salary){
    return salary*2;
}
//设置奖金类
var Bouns = function(){
    this.salary = null;
    this.strategy = null;
};
Bouns.prototype.setSalary = function(salary){
    this.salary=salary;
}
Bouns.prototype.setStrategy = function(strategy){
    console.log("strategy",strategy)
    this.strategy = strategy;
}
Bouns.prototype.getBouns = function(){
    return this.strategy.cal(this.salary);
}
//调用方法
var bounsway = new Bouns();
bounsway.setSalary(5000);
bounsway.setStrategy(new PerformanceS());
console.log(bounsway.getBouns());

如何防止抖动

函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。比如,取输入框的值(每次输入都需要取值)或下拉加载(每次下拉都需要获取底部距离),这样十分的耗费性能。解决方法也是很简单的,每次加载函数的时候,定义一个延长时间。第二次清空重新加载。

var int = jitter(function(ev){
    console.log(ev.target.value);
},2000);
function inputChange(ev){
    int(ev);
}
function jitter(fn,delay){
    var timer = null;
    return function(ev){
        if(timer){
            clearTimeout(timer);
        }
        timer = setTimeout(function(){
            fn(ev);
        }, delay);
    }
}

节流

函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

function throttle(fn,delay){
    var timer = null;
    return function(){
        if(!timer){
            timer = setTimeout(function(){
                fn();
                timer=null;
             }, delay);
          }
      }
}
function handle() {            
    console.log(Math.random());        
}        
window.addEventListener('scroll', throttle(handle, 1000));
上一篇下一篇

猜你喜欢

热点阅读