前端常見面試題(js部分)

Tra發表於2019-01-31

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)

      下面會以詳細的步驟講解上面程式碼的執行:

前端常見面試題(js部分)

       總結:執行棧就是在不停的執行同步程式碼,非同步程式碼會被各自的執行緒維護處理,將各自的回掉函式推入到任務佇列,(所以說任務佇列就是一系列的回掉函式的集合),當執行棧為空時,就會去任務佇列尋找,依次執行對應的回掉函式。    

  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是一個巨集任務,當作根任務,裡面有一個巨集任務一個微任務,在本輪執行完畢後,會先檢查其子輩中有沒有微任務,如果有,則執行。否則,開啟下一個巨集任務。

         畫圖示意:

前端常見面試題(js部分)

總結:在當前的巨集任務中,它總是會先尋找到其內部的微任務執行,然後再執行內部巨集任務,內部的巨集任務再先尋找其內部的微任務執行,然後再執行其內部巨集任務,依次往下。


1.5 組合函式和高階函式

        1.5.1什麼是高階函式?

          它接收函式作為引數或將函式作為輸出返回。

          其實我們已經使用過很多的高階函式,比如filter reduce,所以不要被概念嚇破膽。

        1.5.2什麼是組合函式?

          字面意思就很好理解。多個函式的組合唄。比如管道。

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

// 第一個函式的返回值會傳入到第2個函式的引數裡,執行。是不是很像gulp裡的pipe 
複製程式碼

         上面這些概念不要特別去記,其實我們一直在使用它們。

  

如果有啥問題,請關注我的公眾號留言。您的關注是對我最大的支援!謝謝

                                          前端常見面試題(js部分)


         



相關文章