前端面試題——JS篇

1.介紹一下JS的數(shù)據(jù)類型有那些,值是如何存儲的?

JavaScript一共有8種數(shù)據(jù)類型登钥,其中有7中基本數(shù)據(jù)類型:Undefined、Null娶靡、Boolean牧牢、Number、String姿锭、Symbol(ES6新增塔鳍,表示獨一無二的值)和BigInt(ES10新增);
1種引用數(shù)據(jù)類型——Object(Object本質(zhì)上是由一組無序的名值隊組成)呻此。里面包含function轮纫、Array、Date等趾诗。JavaScript不支持任何創(chuàng)建自定義類型的機制蜡感,而所有值最終都將是上述8種數(shù)據(jù)類型之一蹬蚁。
原始數(shù)據(jù)類型:直接存儲在棧(stack)中,占據(jù)空間小郑兴、大小固定犀斋,屬于頻繁使用數(shù)據(jù),所以放入棧中存儲情连。
引用數(shù)據(jù)類型:同事存儲在棧(stack)堆(heap)中叽粹,占據(jù)空間大、大小不固定却舀。引用數(shù)據(jù)類型在棧中存儲了指針虫几,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時挽拔,會首先檢索其在棧中的地址辆脸,取得地址后從堆中獲取實體。

2.JavaScript的作用域和作用域鏈

作用域:作用域是定義變量的區(qū)域螃诅,它有一套訪問變量的規(guī)則啡氢,這套規(guī)則來管理瀏覽器引擎如何在當前作用域以及嵌套的作用域中根據(jù)變量(標識符)進行變量查找。
作用域鏈:作用域鏈的作用是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問术裸,通過作用域鏈倘是,我們可以訪問到外層環(huán)境的變量和函數(shù)。
作用域鏈的本質(zhì)上是一個指向變量對象的指針列表袭艺。變量對象是一個包含了執(zhí)行環(huán)境中所有變量和函數(shù)的對象搀崭。作用域鏈的前端始終都是當前執(zhí)行上下文的變量對象。全局執(zhí)行上下文的變量對象(也就是全局對象)始終是作用域鏈的最后一個對象猾编。
當我們查找一個變量時瘤睹,如果當前執(zhí)行環(huán)境中沒有找到,我們可以沿著作用域鏈向后查找袍镀。
作用域鏈的創(chuàng)建過程跟執(zhí)行上下文的建立有關(guān)....

3.談?wù)勀銓his默蚌、call、apply和bind的理解

1.在瀏覽器里苇羡,在全局范圍內(nèi)this指向Windows對象绸吸;
2.在函數(shù)中,this永遠指向最后調(diào)用他的那個對象设江;
3.構(gòu)造函數(shù)中锦茁,this指向new出現(xiàn)的那個新的對象;
4.call叉存、apply码俩、bind中的this被強綁定在指定的那個對象上;
5.箭頭函數(shù)中this比較特殊歼捏,箭頭函數(shù)this為父作用域的this稿存,不是調(diào)用時的this要知道前四種方式都是調(diào)用時確定笨篷,也就是動態(tài)的,而箭頭函數(shù)的this指向是靜態(tài)的瓣履,生命的時候就確定了下來率翅;
6.apply、call袖迎、bind都是js給函數(shù)內(nèi)置的一些API冕臭,調(diào)用他們可以為函數(shù)指定this的執(zhí)行,同時也可以傳參燕锥。

4.什么是閉包辜贵,為什么要用它?

閉包是是有權(quán)訪問另一個函數(shù)作用域內(nèi)變量的函數(shù)归形。創(chuàng)建閉包的最常見的方式就是在一個函數(shù)內(nèi)創(chuàng)建另一個函數(shù)托慨,創(chuàng)建的函數(shù)可以訪問到當前函數(shù)的局部變量。
閉包有兩個常用的用途
1.閉包的第一個用途是使我們在函數(shù)外部能訪問到函數(shù)內(nèi)部的變量暇榴。通過使用閉包榴芳,我們可以通過外部調(diào)用閉包函數(shù),從而在外部訪問到函數(shù)內(nèi)部的變量跺撼,可以使用這種方法來創(chuàng)建私有變量。
2.函數(shù)的另一個用途是使已經(jīng)運行結(jié)束的函數(shù)上下文中的變量對象繼續(xù)留在內(nèi)存中讨彼,因為閉包函數(shù)保留了這個變量對象的引用歉井,所以這個變量對象不會被回收。

function a(){
    var n = 0;
    function add(){
       n++;
       console.log(n);
    }
    return add;
}
var a1 = a(); //注意哈误,函數(shù)名只是一個標識(指向函數(shù)的指針)哩至,而()才是執(zhí)行函數(shù);
a1();    //1
a1();    //2  第二次調(diào)用n變量還在內(nèi)存中

其實閉包的本質(zhì)就是作用域鏈的一個特殊的應(yīng)用蜜自,只要了解了作用域鏈的創(chuàng)建過程菩貌,就能夠理解閉包的實現(xiàn)原理。

5.Ajax是什么重荠?如何創(chuàng)建一個Ajax箭阶?

它是一種異步通信的方法,通過直接由 js 腳本向服務(wù)器發(fā)起 http 通信戈鲁,然后根據(jù)服務(wù)器返回的數(shù)據(jù)仇参,更新網(wǎng)頁的相應(yīng)部分,而不用刷新整個頁面的一種方法婆殿。
原生

//1:創(chuàng)建Ajax對象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容IE6及以下版本
//2:配置 Ajax請求地址
xhr.open('get','index.xml',true);
//3:發(fā)送請求
xhr.send(null); // 嚴謹寫法
//4:監(jiān)聽請求诈乒,接受響應(yīng)
xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
          console.log(xhr.responsetXML)
}

JQuery:

$.ajax({
          type:'post',
          url:'',
          async:ture,//async 異步  sync  同步
          data:data,//針對post請求
          dataType:'jsonp',
          success:function (msg) {

          },
          error:function (error) {

          }
        })

promise 封裝實現(xiàn)

// promise 封裝實現(xiàn):

function getJSON(url) {
  // 創(chuàng)建一個 promise 對象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();

    // 新建一個 http 請求
    xhr.open("GET", url, true);

    // 設(shè)置狀態(tài)的監(jiān)聽函數(shù)
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;

      // 當請求成功或失敗時,改變 promise 的狀態(tài)
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };

    // 設(shè)置錯誤監(jiān)聽函數(shù)
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };

    // 設(shè)置響應(yīng)的數(shù)據(jù)類型
    xhr.responseType = "json";

    // 設(shè)置請求頭信息
    xhr.setRequestHeader("Accept", "application/json");

    // 發(fā)送 http 請求
    xhr.send(null);
  });

  return promise;
}

6.簡單介紹一下 V8 引擎的垃圾回收機制

v8 的垃圾回收機制基于分代回收機制婆芦,這個機制又基于世代假說怕磨,這個假說有兩個特點喂饥,一是新生的對象容易早死,另一個是不死的對象會活得更久肠鲫≡卑铮基于這個假說,v8 引擎將內(nèi)存分為了新生代和老生代滩届。

新創(chuàng)建的對象或者只經(jīng)歷過一次的垃圾回收的對象被稱為新生代集侯。經(jīng)歷過多次垃圾回收的對象被稱為老生代。

新生代被分為 From 和 To 兩個空間帜消,To 一般是閑置的棠枉。當 From 空間滿了的時候會執(zhí)行 Scavenge 算法進行垃圾回收。當我們執(zhí)行垃圾回收算法的時候應(yīng)用邏輯將會停止泡挺,等垃圾回收結(jié)束后再繼續(xù)執(zhí)行辈讶。這個算法分為三步:

(1)首先檢查 From 空間的存活對象,如果對象存活則判斷對象是否滿足晉升到老生代的條件娄猫,如果滿足條件則晉升到老生代贱除。如果不滿足條件則移動 To 空間。

(2)如果對象不存活媳溺,則釋放對象的空間月幌。

(3)最后將 From 空間和 To 空間角色進行交換。

新生代對象晉升到老生代有兩個條件:

(1)第一個是判斷是對象否已經(jīng)經(jīng)過一次 Scavenge 回收悬蔽。若經(jīng)歷過扯躺,則將對象從 From 空間復(fù)制到老生代中;若沒有經(jīng)歷蝎困,則復(fù)制到 To 空間录语。

(2)第二個是 To 空間的內(nèi)存使用占比是否超過限制。當對象從 From 空間復(fù)制到 To 空間時禾乘,若 To 空間使用超過 25%澎埠,則對象直接晉升到老生代中。設(shè)置 25% 的原因主要是因為算法結(jié)束后始藕,兩個空間結(jié)束后會交換位置蒲稳,如果 To 空間的內(nèi)存太小,會影響后續(xù)的內(nèi)存分配鳄虱。

老生代采用了標記清除法和標記壓縮法弟塞。標記清除法首先會對內(nèi)存中存活的對象進行標記,標記結(jié)束后清除掉那些沒有標記的對象拙已。由于標記清除后會造成很多的內(nèi)存碎片决记,不便于后面的內(nèi)存分配。所以了解決內(nèi)存碎片的問題引入了標記壓縮法倍踪。

由于在進行垃圾回收的時候會暫停應(yīng)用的邏輯系宫,對于新生代方法由于內(nèi)存小索昂,每次停頓的時間不會太長,但對于老生代來說每次垃圾回收的時間長扩借,停頓會造成很大的影響椒惨。 為了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分為了多步潮罪,每次執(zhí)行完一小步就讓運行邏輯執(zhí)行一會康谆,就這樣交替運行。

7.哪些操作會造成內(nèi)存泄漏嫉到?

1.意外的全局變量
2.被遺忘的計時器或回調(diào)函數(shù)
3.脫離 DOM 的引用
4.閉包
第一種情況是我們由于使用未聲明的變量沃暗,而意外的創(chuàng)建了一個全局變量,而使這個變量一直留在內(nèi)存中無法被回收何恶。
第二種情況是我們設(shè)置了setInterval定時器孽锥,而忘記取消它,如果循環(huán)函數(shù)有對外部變量的引用的話细层,那么這個變量會被一直留在內(nèi)存中惜辑,而無法被回收。
第三種情況是我們獲取一個DOM元素的引用疫赎,而后面這個元素被刪除盛撑,由于我們一直保留了對這個元素的引用,所以它也無法被回收捧搞。
第四種情況是不合理的使用閉包撵彻,從而導(dǎo)致某些變量一直被留在內(nèi)存當中。

8.var实牡、let和const的區(qū)別是什么?

var聲明的變量會掛載在window上轴合,而let和const聲明的變量不會

var a = 100;
console.log(a,window.a);    // 100 100

let b = 10;
console.log(b,window.b);    // 10 undefined

const c = 1;
console.log(c,window.c);    // 1 undefined

var聲明變量存在變量提升创坞,let和const不存在變量提升:

console.log(a); // undefined  ===>  a已聲明還沒賦值,默認得到undefined值
var a = 100;

console.log(b); // 報錯:b is not defined  ===> 找不到b這個變量
let b = 10;

console.log(c); // 報錯:c is not defined  ===> 找不到c這個變量
const c = 10;

let和const聲明形成塊作用域

if(1){
  var a = 100;
  let b = 10;
}

console.log(a); // 100
console.log(b)  // 報錯:b is not defined  ===> 找不到b這個變量

-------------------------------------------------------------

if(1){
  var a = 100;
  const c = 1;
}
console.log(a); // 100
console.log(c)  // 報錯:c is not defined  ===> 找不到c這個變量

同一作用域下let和const不能聲明同名變量受葛,而var可以

var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;

//  控制臺報錯:Identifier 'a' has already been declared  ===> 標識符a已經(jīng)被聲明了题涨。

暫存死區(qū)

var a = 100;

if(1){
    a = 10;
    //在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時总滩,只會在當前作用域找變量a纲堵,
    // 而這時,還未到聲明時候闰渔,所以控制臺Error:a is not defined
    let a = 1;
}

const

/*
*   1席函、一旦聲明必須賦值,不能使用null占位。
*
*   2冈涧、聲明后不能再修改
*
*   3茂附、如果聲明的是復(fù)合類型數(shù)據(jù)正蛙,可以修改其屬性
*
* */

const a = 100; 

const list = [];
list[0] = 10;
console.log(list);  // [10]

const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}

9.什么是箭頭函數(shù)?

箭頭函數(shù)表達式的語法比函數(shù)表達式更簡潔营曼,并且沒有自己的this乒验,arguments,super或new.target蒂阱。箭頭函數(shù)表達式更適用于那些本來需要匿名函數(shù)的地方锻全,并且它不能用作構(gòu)造函數(shù)。

var getCurrentDate = function (){
  return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();

在本例中录煤,ES5 版本中有function(){}聲明和return關(guān)鍵字鳄厌,這兩個關(guān)鍵字分別是創(chuàng)建函數(shù)和返回值所需要的。在箭頭函數(shù)版本中辐赞,我們只需要()括號部翘,不需要 return 語句,因為如果我們只有一個表達式或值需要返回响委,箭頭函數(shù)就會有一個隱式的返回新思。

//ES5 Version
function greet(name) {
  return 'Hello ' + name + '!';
}

//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;

我們還可以在箭頭函數(shù)中使用與函數(shù)表達式和函數(shù)聲明相同的參數(shù)。如果我們在一個箭頭函數(shù)中有一個參數(shù)赘风,則可以省略括號夹囚。

const getArgs = () => arguments

const getArgs2 = (...rest) => rest

箭頭函數(shù)不能訪問arguments對象。所以調(diào)用第一個getArgs函數(shù)會拋出一個錯誤邀窃。相反荸哟,我們可以使用rest參數(shù)來獲得在箭頭函數(shù)中傳遞的所有參數(shù)。

const data = {
  result: 0,
  nums: [1, 2, 3, 4, 5],
  computeResult() {
    // 這里的“this”指的是“data”對象
    const addAll = () => {
      return this.nums.reduce((total, cur) => total + cur, 0)
    };
    this.result = addAll();
  }
};

箭頭函數(shù)沒有自己的this值瞬捕。它捕獲詞法作用域函數(shù)的this值鞍历,在此示例中,addAll函數(shù)將復(fù)制computeResult 方法中的this值肪虎,如果我們在全局作用域聲明箭頭函數(shù)劣砍,則this值為 window 對象。

10. js的深淺拷貝

JavaScript的深淺拷貝一直是個難點扇救,如果現(xiàn)在面試官讓我寫一個深拷貝刑枝,我可能也只是能寫出個基礎(chǔ)版的。所以在寫這條之前我拜讀了收藏夾里各路大佬寫的博文迅腔。具體可以看下面我貼的鏈接装畅,這里只做簡單的總結(jié)。
淺拷貝:創(chuàng)建一個新對象沧烈,這個對象有著原始對象屬性值的一份精確拷貝掠兄。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型徽千,拷貝的就是內(nèi)存地址 苫费,所以如果其中一個對象改變了這個地址,就會影響到另一個對象双抽。
深拷貝:將一個對象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,且修改新對象不會影響原對象百框。
淺拷貝的實現(xiàn)方式
Object.assign()方法: 用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標對象。它將返回目標對象牍汹。
** Array.prototype.slice():slice() 方法**:返回一個新的數(shù)組對象铐维,這一對象是一個由 begin和end(不包括end)決定的原數(shù)組的淺拷貝。原始數(shù)組不會被改變慎菲。
拓展運算符 ...:

let a = {
    name: "Jake",
    flag: {
        title: "better day by day",
        time: "2020-05-31"
    }
}
let b = {...a};

深拷貝的實現(xiàn)方式
乞丐版: JSON.parse(JSON.stringify(object))嫁蛇,缺點諸多(會忽略undefined、symbol露该、函數(shù)睬棚;不能解決循環(huán)引用;不能處理正則解幼、new Date())
基礎(chǔ)版: 淺拷貝+遞歸 (只考慮了普通的 object和 array兩種數(shù)據(jù)類型)

function cloneDeep(target,map = new WeakMap()) {
  if(typeOf taret ==='object'){
     let cloneTarget = Array.isArray(target) ? [] : {};
      
     if(map.get(target)) {
        return target;
    }
     map.set(target, cloneTarget);
     for(const key in target){
        cloneTarget[key] = cloneDeep(target[key], map);
     }
     return cloneTarget
  }else{
       return target
  }
 
}

終極版

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];


function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}

function isObject(target) {
    const type = typeof target;
    return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
    return Object.prototype.toString.call(target);
}

function getInit(target) {
    const Ctor = target.constructor;
    return new Ctor();
}

function cloneSymbol(targe) {
    return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
    const reFlags = /\w*$/;
    const result = new targe.constructor(targe.source, reFlags.exec(targe));
    result.lastIndex = targe.lastIndex;
    return result;
}

function cloneFunction(func) {
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            if (param) {
                const paramArr = param[0].split(',');
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        return eval(funcString);
    }
}

function cloneOtherType(targe, type) {
    const Ctor = targe.constructor;
    switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
            return new Ctor(targe);
        case regexpTag:
            return cloneReg(targe);
        case symbolTag:
            return cloneSymbol(targe);
        case funcTag:
            return cloneFunction(targe);
        default:
            return null;
    }
}

function clone(target, map = new WeakMap()) {

    // 克隆原始類型
    if (!isObject(target)) {
        return target;
    }

    // 初始化
    const type = getType(target);
    let cloneTarget;
    if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
    } else {
        return cloneOtherType(target, type);
    }

    // 防止循環(huán)引用
    if (map.get(target)) {
        return map.get(target);
    }
    map.set(target, cloneTarget);

    // 克隆set
    if (type === setTag) {
        target.forEach(value => {
            cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆map
    if (type === mapTag) {
        target.forEach((value, key) => {
            cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
    }

    // 克隆對象和數(shù)組
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;
}

module.exports = {
    clone
};

11.什么是回調(diào)函數(shù)抑党?回調(diào)函數(shù)有什么缺點

回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個參數(shù)傳遞給其他的代碼撵摆,其作用是在需要的時候方便調(diào)用這段(回調(diào)函數(shù))代碼底靠。
在JavaScript中函數(shù)也是對象的一種,同樣對象可以作為參數(shù)傳遞給函數(shù)特铝,因此函數(shù)也可以作為參數(shù)傳遞給另外一個函數(shù)暑中,這個作為參數(shù)的函數(shù)就是回調(diào)函數(shù)。

const btnAdd = document.getElementById('btnAdd');

btnAdd.addEventListener('click', function clickCallback(e) {
    // do something useless
});

在本例中鲫剿,我們等待id為btnAdd的元素中的click事件鳄逾,如果它被單擊,則執(zhí)行clickCallback函數(shù)灵莲⊙铣模回調(diào)函數(shù)向某些數(shù)據(jù)或事件添加一些功能。

回調(diào)函數(shù)有一個致命的弱點笆呆,就是容易寫出回調(diào)地獄(Callback hell)。假設(shè)多個事件存在依賴性:

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
    
        },3000)
    
    },2000)
},1000)

這就是典型的回調(diào)地獄粱挡,以上代碼看起來不利于閱讀和維護赠幕,事件一旦多起來就更是亂糟糟,所以在es6中提出了Promise和async/await來解決回調(diào)地獄的問題询筏。當然榕堰,回調(diào)函數(shù)還存在著別的幾個缺點,比如不能使用 try catch 捕獲錯誤,不能直接 return逆屡。

12.Promise是什么圾旨,可以手寫實現(xiàn)一下嗎?

Promise魏蔗,翻譯過來是承諾砍的,承諾它過一段時間會給你一個結(jié)果。從編程講Promise 是異步編程的一種解決方案莺治。下面是Promise在MDN的相關(guān)說明:

Promise 對象是一個代理對象(代理一個值)廓鞠,被代理的值在Promise對象創(chuàng)建時可能是未知的。它允許你為異步操作的成功和失敗分別綁定相應(yīng)的處理方法(handlers)谣旁。這讓異步方法可以像同步方法那樣返回值床佳,但并不是立即返回最終執(zhí)行結(jié)果,而是一個能代表未來出現(xiàn)的結(jié)果的promise對象榄审。

一個 Promise有以下幾種狀態(tài):

pending: 初始狀態(tài)砌们,既不是成功,也不是失敗狀態(tài)搁进。
fulfilled: 意味著操作成功完成浪感。
rejected: 意味著操作失敗。
這個承諾一旦從等待狀態(tài)變成為其他狀態(tài)就永遠不能更改狀態(tài)了拷获,也就是說一旦狀態(tài)變?yōu)?fulfilled/rejected 后篮撑,就不能再次改變〈夜希可能光看概念大家不理解Promise赢笨,我們舉個簡單的栗子;

假如我有個女朋友驮吱,下周一是她生日茧妒,我答應(yīng)她生日給她一個驚喜,那么從現(xiàn)在開始這個承諾就進入等待狀態(tài)左冬,等待下周一的到來桐筏,然后狀態(tài)改變。如果下周一我如約給了女朋友驚喜拇砰,那么這個承諾的狀態(tài)就會由pending切換為fulfilled梅忌,表示承諾成功兌現(xiàn),一旦是這個結(jié)果了除破,就不會再有其他結(jié)果牧氮,即狀態(tài)不會在發(fā)生改變;反之如果當天我因為工作太忙加班瑰枫,把這事給忘了踱葛,說好的驚喜沒有兌現(xiàn),狀態(tài)就會由pending切換為rejected,時間不可倒流尸诽,所以狀態(tài)也不能再發(fā)生變化甥材。

上一條我們說過Promise可以解決回調(diào)地獄的問題,沒錯性含,pending 狀態(tài)的 Promise 對象會觸發(fā) fulfilled/rejected 狀態(tài)洲赵,一旦狀態(tài)改變,Promise 對象的 then 方法就會被調(diào)用胶滋;否則就會觸發(fā) catch板鬓。我們將上一條回調(diào)地獄的代碼改寫一下:

new Promise((resolve,reject) => {
     setTimeout(() => {
            console.log(1)
            resolve()
        },1000)
        
}).then((res) => {
    setTimeout(() => {
            console.log(2)
        },2000)
}).then((res) => {
    setTimeout(() => {
            console.log(3)
        },3000)
}).catch((err) => {
console.log(err)
})

其實Promise也是存在一些缺點的究恤,比如無法取消 Promise俭令,錯誤需要通過回調(diào)函數(shù)捕獲。

promise手寫實現(xiàn)部宿,面試夠用版

function myPromise(constructor){
    let self=this;
    self.status="pending" //定義狀態(tài)改變前的初始狀態(tài)
    self.value=undefined;//定義狀態(tài)為resolved的時候的狀態(tài)
    self.reason=undefined;//定義狀態(tài)為rejected的時候的狀態(tài)
    function resolve(value){
        //兩個==="pending"抄腔,保證了狀態(tài)的改變是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //兩個==="pending",保證了狀態(tài)的改變是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕獲構(gòu)造異常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
// 定義鏈式調(diào)用的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:       
   }
}

13.什么是 async/await 及其如何工作,有什么優(yōu)缺點理张?

async/await是一種建立在Promise之上的編寫異步或非阻塞代碼的新方法赫蛇,被普遍認為是 JS異步操作的最終且最優(yōu)雅的解決方案。相對于 Promise 和回調(diào)雾叭,它的可讀性和簡潔度都更高悟耘。畢竟一直then()也很煩。

async 是異步的意思织狐,而 awaitasync wait的簡寫暂幼,即異步等待。

所以從語義上就很好理解 async 用于聲明一個 function 是異步的移迫,而await 用于等待一個異步方法執(zhí)行完成旺嬉。

一個函數(shù)如果加上 async ,那么該函數(shù)就會返回一個 Promise

async function test() {
  return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}

可以看到輸出的是一個Promise對象厨埋。所以邪媳,async 函數(shù)返回的是一個 Promise 對象,如果在 async 函數(shù)中直接 return 一個直接量荡陷,async 會把這個直接量通過 PromIse.resolve()封裝成Promise對象返回噪伊。

相比于Promise懂扼,async/await能更好地處理 then 鏈

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

現(xiàn)在分別用 Promiseasync/await來實現(xiàn)這三個步驟的處理疾宏。

使用Promise

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900

使用async/await

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
}
doIt();

結(jié)果和之前的 Promise 實現(xiàn)是一樣的担映,但是這個代碼看起來是不是清晰得多技扼,優(yōu)雅整潔诸狭,幾乎跟同步代碼一樣专甩。
await關(guān)鍵字只能在async function中使用遮晚。在任何非async function的函數(shù)中使用await關(guān)鍵字都會拋出錯誤。await關(guān)鍵字在執(zhí)行下一行代碼之前等待右側(cè)表達式(可能是一個Promise)返回渣蜗。
優(yōu)缺點
async/await的優(yōu)勢在于處理 then 的調(diào)用鏈屠尊,能夠更清晰準確的寫出代碼,并且也能優(yōu)雅地解決回調(diào)地獄問題耕拷。當然也存在一些缺點讼昆,因為 await 將異步代碼改造成了同步代碼,如果多個異步代碼沒有依賴性卻使用了 await 會導(dǎo)致性能上的降低骚烧。

14.js 的節(jié)流與防抖

函數(shù)防抖 是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào)浸赫,如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計時赃绊。這可以使用在一些點擊請求的事件上既峡,避免因為用戶的多次點擊向后端發(fā)送多次請求。

函數(shù)節(jié)流 是指規(guī)定一個單位時間碧查,在這個單位時間內(nèi)运敢,只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次忠售,只有一次能生效传惠。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過事件節(jié)流來降低事件調(diào)用的頻率稻扬。

// 函數(shù)防抖的實現(xiàn)
function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = arguments;

    // 如果此時存在定時器的話卦方,則取消之前的定時器重新記時
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    // 設(shè)置定時器,使事件間隔指定事件后執(zhí)行
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

// 函數(shù)節(jié)流的實現(xiàn);
function throttle(fn, delay) {
  var preTime = Date.now();

  return function() {
    var context = this,
      args = arguments,
      nowTime = Date.now();

    // 如果兩次時間間隔超過了指定時間泰佳,則執(zhí)行函數(shù)盼砍。
    if (nowTime - preTime >= delay) {
      preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乐纸,隨后出現(xiàn)的幾起案子衬廷,更是在濱河造成了極大的恐慌,老刑警劉巖汽绢,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吗跋,死亡現(xiàn)場離奇詭異,居然都是意外死亡宁昭,警方通過查閱死者的電腦和手機跌宛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來积仗,“玉大人疆拘,你說我怎么就攤上這事〖挪埽” “怎么了哎迄?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵回右,是天一觀的道長。 經(jīng)常有香客問我漱挚,道長翔烁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任旨涝,我火速辦了婚禮蹬屹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘白华。我一直安慰自己慨默,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布弧腥。 她就那樣靜靜地躺著厦取,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸟赫。 梳的紋絲不亂的頭發(fā)上蒜胖,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音抛蚤,去河邊找鬼台谢。 笑死,一個胖子當著我的面吹牛岁经,可吹牛的內(nèi)容都是我干的朋沮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼缀壤,長吁一口氣:“原來是場噩夢啊……” “哼樊拓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起塘慕,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤筋夏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后图呢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體条篷,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年蛤织,在試婚紗的時候發(fā)現(xiàn)自己被綠了赴叹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡指蚜,死狀恐怖乞巧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摊鸡,我是刑警寧澤绽媒,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布蚕冬,位于F島的核電站,受9級特大地震影響是辕,放射性物質(zhì)發(fā)生泄漏播瞳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一免糕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忧侧,春花似錦石窑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肯夏,卻和暖如春经宏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驯击。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工烁兰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徊都。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓沪斟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親暇矫。 傳聞我的和親對象是個殘疾皇子主之,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345