前端常见面试题(js部分)

1.1 new做了哪些操作

function Parent(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}

var a = new Parent('ljy',18,'男');
console.log(a); //Parent { name: 'ljy', age: 18, sex: '男' }

/*
* 我们想一下
* 1.在Parent函数中,其实并没有返回值,为什么打印中有返回值
* 2.为什么就生成了一个对象
* */

// 自己实现
function _new(prototypeFn,name,age,sex){
     // 首先将arguments 转化为 数组
    var arr = Array.prototype.slice.call(arguments);
    var prototypeFn = arr.shift();
    var obj = {};
    obj.__proto__ = prototypeFn.prototype;
    var returnText = prototypeFn.apply(obj,arr);

    // 另外:如果构造函数一个新对象,则返回这个新对象,否则返回obj
    // 如果返回number string boolean等,new方法会忽略该返回值,返回obj
    // 如果构造函数没有返回,则默认返回obj

    if(typeof returnText === 'object' && returnText != null){
        return returnText
    }else{
        return obj;
    }
}

var b= _new(Parent,'ljy',18,'男');
console.log(b); //Parent { name: 'ljy', age: 18, sex: '男' }复制代码

总结:new干了什么?

创建一个空对象,作为将要返回的对象实例。
将这个空对象的原型,指向构造函数的prototype属性。
将这个空对象赋值给函数内部的this关键字。
开始执行构造函数内部的代码。复制代码

1.2 apply,call,bind异同,并实现内部实现原理

/*
* 首先说三者的相同点:
* 都是为了改变函数体内的this指向
*
* 不同点:
* call():第一个参数是要this要指向的对象,后面的参数则按照参数顺序传递进去
* apply():第一个参数是要this要指向的对象,后面的参数以数组格式传递进去
* bind():第一个参数是要this要指向的对象,但是其返回的依然是函数
*/

Function.prototype.myCall = function () {
    var args = Array.prototype.slice.call(arguments);
    // this指向
    var context = args.shift();
    if(context ==  null){
        context = window
    }
    context.fn= this;
    // 这儿有个问题,如果args是个数组,如[1,2,3],args.toString()会转为"1,2,3" 将其看成一个整体,会造成bug
    // var result = context.fn(args.toString());

    // 解决上述问题,需使用eval方法
    if(args.length){
        var arr = [];
        for(var i= 0; i < args.length; i++){
            arr.push('args['+i+']');
        }
        var result = eval("context.fn(" + arr.toString() + ")");
    }else{
        var result = context['fn']();
    }

    delete context.fn;

    return result;

}

Function.prototype.myApply = function () {
    var args = Array.prototype.slice.call(arguments);
    // this指向
    var context = args.shift();
    // 因为apply函数第2个参数是数组,则如果还有第3个参数,则warn
    if(args.length > 1){
        throw new Error('apply函数只能有两个参数');
        return false;
    }
    if(context ==  null){
        context = window
    }
    context.fn= this;

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

    return result;

}


Function.prototype.myBind = function () {
    var args = Array.prototype.slice.call(arguments);
    var context =  args.shift();
    var _this = this;
    return function () {
        return _this.apply(context,args.concat(Array.prototype.slice.call(arguments)))
    }
}



var a = {
    name:'ljy',
    log:function(x,y){
        console.log(this.name);
        return this.name+x+y
    }
}

var name = 'qianduan'
console.log(a.log.myCall(null,'24','男'))
console.log(a.log.myApply(null,['24','男']))
console.log(a.log.myBind(null)('24','男'))复制代码

1.3 回收机制

1.31概念

      JavaScript具有自动垃圾收集机制。

1.4 宏任务微任务

1.4.1 前置知识

       js引擎是如何执行我们的代码的呢?是一行一行逐步执行么?

// 以最简单的代码示意:
console.log('start');
setTimeOut(function(){
console.log('timer')
},0)
console.log('end');


// 打印结果会是  start  timer end 吗?复制代码

      我们代码分为同步异步代码,常见的异步的代码 定时器 ajax 事件绑定等等。同步代码就是我们常见的变量定义  for  if 等等。

      那js引擎是如何执行代码的呢?

      按照最简单的解释,同步代码会推入执行栈,定时器异步代码则由定时触发器线程(这里注意:js是单线程,但不代表浏览器引擎就是单线程,定时器会由定时器线程维护处理。后面会讲到浏览器的多进程,这里只要知道ajax啊,定时器啊,都有一个各自的线程维护)触发,并将其回调函数推入到 “任务队列”,当执行栈执行结束后,就去任务队列中寻找,依次执行,这就是所谓的“事件轮询”(event loop)

      下面会以详细的步骤讲解上面代码的执行:


       总结:执行栈就是在不停的执行同步代码,异步代码会被各自的线程维护处理,将各自的回掉函数推入到任务队列,(所以说任务队列就是一系列的回掉函数的集合),当执行栈为空时,就会去任务队列寻找,依次执行对应的回掉函数。    

  1.4.2 宏任务微任务

       其实我们发现,上面的广义的同步异步,上面的解释足够了,但是ES6之后,出现了promise等,在标准中,将promise认定为“微任务”。(微任务是浏览器层面的处理,所以我们使用babel-pollfill这种其实是无法模拟微任务的,所以在babel-pollfill中对promise的实现其实是以宏任务处理的)

       那宏任务包含什么,微任务包含什么?

  •    宏任务:整体代码script,setTimeout,setInterval    
  •    微任务:Promise,process.nextTick
      那他们的执行顺序又是什么?

       可以简单认为这是剥洋葱的顺序

console.log('1');

setTimeout(function() {
    console.log('2');
     new Promise(function(resolve) {
        console.log('3');
        resolve();
    }).then(function() {
        console.log('4')
    })
})

new Promise(function(resolve) {
    console.log('5');
    resolve();
}).then(function() {
    console.log('6')
})

// 上述代码会如何执行?复制代码

       解释:

       1.当整体的script作为第一个宏任务进入主线程,会打印出1

       2.遇到定时器,又是个宏任务,定时器线程在制定ms后将其回掉函数推入到宏任务任务队列。

      3.遇到微任务Promise,会先打印出5,为什么?因为这一步是同步代码,真正的微任务是then里面的回掉函数。(笔者写过promise的实现过程,详细点击https://juejin.im/post/5c27130f6fb9a049ef26a936)

        在本轮的事件循环中,整体的script是一个宏任务,当作根任务,里面有一个宏任务一个微任务,在本轮执行完毕后,会先检查其子辈中有没有微任务,如果有,则执行。否则,开启下一个宏任务。

         画图示意:


总结:在当前的宏任务中,它总是会先寻找到其内部的微任务执行,然后再执行内部宏任务,内部的宏任务再先寻找其内部的微任务执行,然后再执行其内部宏任务,依次往下。


1.5 组合函数和高阶函数

        1.5.1什么是高阶函数?

          它接收函数作为参数或将函数作为输出返回。

          其实我们已经使用过很多的高阶函数,比如filter reduce,所以不要被概念吓破胆。

        1.5.2什么是组合函数?

          字面意思就很好理解。多个函数的组合呗。比如管道。

function compose(fn1,fn2){
    return function(arg){
     fn2(fn1(arg));
    }
}

// 第一个函数的返回值会传入到第2个函数的参数里,执行。是不是很像gulp里的pipe 
复制代码

         上面这些概念不要特别去记,其实我们一直在使用它们。

  

如果有啥问题,请关注我的公众号留言。您的关注是对我最大的支持!谢谢

                                         


         



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值