30k

081.什么是 Polyfill 辜纲?

Polyfill 指的是用于實現瀏覽器并不支持的原生 API 的代碼巧婶。

比如說 querySelectorAll 是很多現代瀏覽器都支持的原生 Web API搪桂,但是有些古老的瀏覽器并不支持罐寨,那么假設有人寫了一段代碼來實現這個功能使這些瀏覽器也支持了這個功能测垛,那么這就可以成為一個 Polyfill萝究。

一個 shim 是一個庫唇辨,有自己的 API廊酣,而不是單純實現原生不支持的 API。

082. 使用 JS 實現獲取文件擴展名赏枚?

// String.lastIndexOf() 方法返回指定值(本例中的'.')在調用該方法的字符串中最后出現的位置亡驰,如果沒找到則返回 -1。

// 對于 'filename' 和 '.hiddenfile' 饿幅,lastIndexOf 的返回值分別為 0 和 -1 無符號右移操作符(>>>) 將 -1 轉換為 4294967295 凡辱,將 -2 轉換為 4294967294 ,這個方法可以保證邊緣情況時文件名不變栗恩。

// String.prototype.slice() 從上面計算的索引處提取文件的擴展名透乾。如果索引比文件名的長度大,結果為""磕秤。
function getFileExtension(filename) {
return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
}

083.使用 JS 實現獲取文件擴展名乳乌?

函數防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調,如果在這 n 秒內事件又被觸發(fā)市咆,則重新計時汉操。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發(fā)送多次請求床绪。

函數節(jié)流是指規(guī)定一個單位時間客情,在這個單位時間內,只能有一次觸發(fā)事件的回調函數執(zhí)行癞己,如果在同一個單位時間內某事件被觸發(fā)多次膀斋,只有一次能生效。節(jié)流可以使用在 scroll 函數的事件監(jiān)聽上痹雅,通過事件節(jié)流來降低事件調用的頻率仰担。

084. escape,encodeURI,encodeURIComponent 有什么區(qū)別?

相關知識點:
escape 和 encodeURI 都屬于 Percent-encoding绩社,基本功能都是把 URI 非法字符轉化成合法字符摔蓝,轉化后形式類似「%*」。
它們的根本區(qū)別在于愉耙,escape 在處理 0xff 之外字符的時候贮尉,是直接使用字符的 unicode 在前面加上一個「%u」,而 encode URI 則是先進行 UTF-8朴沿,再在 UTF-8 的每個字節(jié)碼前加上一個「%」猜谚;在處理 0xff 以內字符時败砂,編碼方式是一樣的(都是「%XX」,XX 為字符的 16 進制 unicode魏铅,同時也是字符的 UTF-8)昌犹,只是范圍(即哪些字符編碼哪些字符不編碼)不一樣。
回答:
encodeURI 是對整個 URI 進行轉義览芳,將 URI 中的非法字符轉換為合法字符斜姥,所以對于一些在 URI 中有特殊意義的字符不會進行轉義。

encodeURIComponent 是對 URI 的組成部分進行轉義沧竟,所以一些特殊字符也會得到轉義铸敏。

escape 和 encodeURI 的作用相同,不過它們對于 unicode 編碼為 0xff 之外字符的時候會有區(qū)別屯仗,escape 是直接在字符的 unicode 編碼前加上 %u搞坝,而 encodeURI 首先會將字符轉換為 UTF-8 的格式,再在每個字節(jié)前加上 %魁袜。

085.Unicode 和 UTF-8 之間的關系?

Unicode 是一種字符集合敦第,現在可容納 100 多萬個字符峰弹。每個字符對應一個不同的 Unicode 編碼,它只規(guī)定了符號的二進制代碼芜果,卻沒有規(guī)定這個二進制代碼在計算機中如何編碼傳輸鞠呈。

UTF-8 是一種對 Unicode 的編碼方式,它是一種變長的編碼方式右钾,可以用 1~4 個字節(jié)來表示一個字符蚁吝。

086.js 的事件循環(huán)是什么?

相關知識點:
事件隊列是一個存儲著待執(zhí)行任務的隊列舀射,其中的任務嚴格按照時間先后順序執(zhí)行窘茁,排在隊頭的任務將會率先執(zhí)行,而排在隊尾的任務會最后執(zhí)行脆烟。事件隊列每次僅執(zhí)行一個任務山林,在該任務執(zhí)行完畢之后,再執(zhí)行下一個任務邢羔。執(zhí)行棧則是一個類似于函數調用棧的運行容器驼抹,當執(zhí)行棧為空時,JS 引擎便檢查事件隊列拜鹤,如果不為空的話框冀,事件隊列便將第一個任務壓入執(zhí)行棧中運行。
回答:
因為 js 是單線程運行的敏簿,在代碼執(zhí)行的時候明也,通過將不同函數的執(zhí)行上下文壓入執(zhí)行棧中來保證代碼的有序執(zhí)行。在執(zhí)行同步代碼的時候,如果遇到了異步事件诡右,js 引擎并不會一直等待其返回結果安岂,而是會將這個事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務帆吻。當異步事件執(zhí)行完畢后域那,再將異步事件對應的回調加入到與當前執(zhí)行棧中不同的另一個任務隊列中等待執(zhí)行。任務隊列可以分為宏任務對列和微任務對列猜煮,當當前執(zhí)行棧中的事件執(zhí)行完畢后次员,js 引擎首先會判斷微任務對列中是否有任務可以執(zhí)行王带,如果有就將微任務隊首的事件壓入棧中執(zhí)行淑蔚。當微任務對列中的任務都執(zhí)行完成后再去判斷宏任務對列中的任務。

微任務包括了 promise 的回調愕撰、node 中的 process.nextTick 刹衫、對 Dom 變化監(jiān)聽的 MutationObserver。

宏任務包括了 script 腳本的執(zhí)行搞挣、setTimeout 带迟,setInterval ,setImmediate 一類的定時事件囱桨,還有如 I/O 操作仓犬、UI 渲
染等。

087. js 中的深淺拷貝實現舍肠?

相關資料:

// 淺拷貝的實現;

function shallowCopy(object) {
  // 只拷貝對象
  if (!object || typeof object !== "object") return;

  // 根據 object 的類型判斷是新建一個數組還是對象
  let newObject = Array.isArray(object) ? [] : {};

  // 遍歷 object搀继,并且判斷是 object 的屬性才拷貝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }

  return newObject;
}

// 深拷貝的實現;

function deepCopy(object) {
  if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}

回答:
淺拷貝指的是將一個對象的屬性值復制到另一個對象,如果有的屬性的值為引用類型的話翠语,那么會將這個引用的地址復制給對象叽躯,因此兩個對象會有同一個引用類型的引用。淺拷貝可以使用 Object.assign 和展開運算符來實現。

深拷貝相對淺拷貝而言,如果遇到屬性值為引用類型的時候身堡,它新建一個引用類型并將對應的值復制給它,因此對象獲得的一個新的引用類型而不是一個原有類型的引用畔况。深拷貝對于一些對象可以使用 JSON 的兩個函數來實現,但是由于 JSON 的對象格式比 js 的對象格式更加嚴格慧库,所以如果屬性值里邊出現函數或者 Symbol 類型的值時跷跪,會轉換失敗

088. 手寫 call、apply 及 bind 函數

相關資料:

// call函數實現
Function.prototype.myCall = function(context) {
  // 判斷調用對象
  if (typeof this !== "function") {
    console.error("type error");
  }

  // 獲取參數
  let args = [...arguments].slice(1),
    result = null;

  // 判斷 context 是否傳入齐板,如果未傳入則設置為 window
  context = context || window;

  // 將調用函數設為對象的方法
  context.fn = this;

  // 調用函數
  result = context.fn(...args);

  // 將屬性刪除
  delete context.fn;

  return result;
};

// apply 函數實現

Function.prototype.myApply = function(context) {
  // 判斷調用對象是否為函數
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  let result = null;

  // 判斷 context 是否存在吵瞻,如果未傳入則為 window
  context = context || window;

  // 將函數設為對象的方法
  context.fn = this;

  // 調用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }

  // 將屬性刪除
  delete context.fn;

  return result;
};

// bind 函數實現
Function.prototype.myBind = function(context) {
  // 判斷調用對象是否為函數
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  // 獲取參數
  var args = [...arguments].slice(1),
    fn = this;

  return function Fn() {
    // 根據調用方式葛菇,傳入不同綁定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

回答:
call 函數的實現步驟:
1.判斷調用對象是否為函數,即使我們是定義在函數的原型上的橡羞,但是可能出現使用 call 等方式調用的情況眯停。
2.判斷傳入上下文對象是否存在,如果不存在卿泽,則設置為 window 莺债。
3.處理傳入的參數,截取第一個參數后的所有參數签夭。
4.將函數作為上下文對象的一個屬性齐邦。
5.使用上下文對象來調用這個方法,并保存返回結果第租。
6.刪除剛才新增的屬性措拇。
7.返回結果。
apply 函數的實現步驟:
1.判斷調用對象是否為函數慎宾,即使我們是定義在函數的原型上的丐吓,但是可能出現使用 call 等方式調用的情況。
2.判斷傳入上下文對象是否存在趟据,如果不存在汰蜘,則設置為 window 。
3.將函數作為上下文對象的一個屬性之宿。
4.判斷參數值是否傳入
4.使用上下文對象來調用這個方法,并保存返回結果苛坚。
5.刪除剛才新增的屬性
6.返回結果
bind 函數的實現步驟:
1.判斷調用對象是否為函數比被,即使我們是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況泼舱。
2.保存當前函數的引用等缀,獲取其余傳入參數值。
3.創(chuàng)建一個函數返回
4.函數內部使用 apply 來綁定函數調用娇昙,需要判斷函數作為構造函數的情況尺迂,這個時候需要傳入當前函數的 this 給 apply 調用,其余情況都傳入指定的上下文對象冒掌。
詳細資料可以參考:
《手寫 call噪裕、apply 及 bind 函數》
《JavaScript 深入之 call 和 apply 的模擬實現》

089. 函數柯里化的實現

// 函數柯里化指的是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。

function curry(fn, args) {
  // 獲取函數需要的參數長度
  let length = fn.length;

  args = args || [];

  return function() {
    let subArgs = args.slice(0);

    // 拼接得到現有的所有參數
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }

    // 判斷參數的長度是否已經滿足函數所需參數的長度
    if (subArgs.length >= length) {
      // 如果滿足股毫,執(zhí)行函數
      return fn.apply(this, subArgs);
    } else {
      // 如果不滿足膳音,遞歸返回科里化的函數,等待參數的傳入
      return curry.call(this, fn, subArgs);
    }
  };
}

// es6 實現
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

090. 為什么 0.1 + 0.2 != 0.3铃诬?如何解決這個問題祭陷?

當計算機計算 0.1+0.2 的時候苍凛,實際上計算的是這兩個數字在計算機里所存儲的二進制,0.1 和 0.2 在轉換為二進制表示的時候會出現位數無限循環(huán)的情況兵志。js 中是以 64 位雙精度格式來存儲數字的醇蝴,只有 53 位的有效數字,超過這個長度的位數會被截取掉這樣就造成了精度丟失的問題想罕。這是第一個會造成精度丟失的地方悠栓。在對兩個以 64 位雙精度格式的數據進行計算的時候,首先會進行對階的處理弧呐,對階指的是將階碼對齊闸迷,也就是將小數點的位置對齊后,再進行計算俘枫,一般是小階向大階對齊腥沽,因此小階的數在對齊的過程中,有效數字會向右移動鸠蚪,移動后超過有效位數的位會被截取掉今阳,這是第二個可能會出現精度丟失的地方。當兩個數據階碼對齊后茅信,進行相加運算后盾舌,得到的結果可能會超過 53 位有效數字,因此超過的位數也會被截取掉蘸鲸,這是可能發(fā)生精度丟失的第三個地方妖谴。

對于這樣的情況,我們可以將其轉換為整數后再進行運算酌摇,運算后再轉換為對應的小數膝舅,以這種方式來解決這個問題。

我們還可以將兩個數相加的結果和右邊相減窑多,如果相減的結果小于一個極小數仍稀,那么我們就可以認定結果是相等的,這個極小數可以
使用 es6 的 Number.EPSILON
詳細資料可以參考:
《十進制的 0.1 為什么不能用二進制很好的表示埂息?》
《十進制浮點數轉成二進制》
《浮點數的二進制表示》
《js 浮點數存儲精度丟失原理》
《浮點數精度之謎》
《JavaScript 浮點數陷阱及解法》
《0.1+0.2 !== 0.3技潘?》
《JavaScript 中奇特的~運算符》

091. 原碼、反碼和補碼的介紹

原碼是計算機中對數字的二進制的定點表示方法千康,最高位表示符號位享幽,其余位表示數值位。優(yōu)點是易于分辨吧秕,缺點是不能夠直接參與運算琉闪。

正數的反碼和其原碼一樣;負數的反碼砸彬,符號位為1颠毙,數值部分按原碼取反斯入。
如 [+7]原 = 00000111,[+7]反 = 00000111蛀蜜;[-7]原 = 10000111刻两,[-7]反 = 11111000。

正數的補碼和其原碼一樣滴某;負數的補碼為其反碼加1磅摹。

例如 [+7]原 = 00000111,[+7]反 = 00000111霎奢,[+7]補 = 00000111户誓;
[-7]原 = 10000111,[-7]反 = 11111000幕侠,[-7]補 = 11111001

之所以在計算機中使用補碼來表示負數的原因是帝美,這樣可以將加法運算擴展到所有的數值計算上,因此在數字電路中我們只需要考慮加法器的設計就行了晤硕,而不用再為減法設置新的數字電路悼潭。
詳細資料可以參考:
《關于 2 的補碼》

092. toPrecision 和 toFixed 和 Math.round 的區(qū)別?

toPrecision 用于處理精度舞箍,精度是從左至右第一個不為 0 的數開始數起舰褪。
toFixed 是對小數點后指定位數取整,從小數點開始數起疏橄。
Math.round 是將一個數字四舍五入到一個整數占拍。

093 什么是 XSS 攻擊?如何防范 XSS 攻擊捎迫?

XSS 攻擊指的是跨站腳本攻擊刷喜,是一種代碼注入攻擊。攻擊者通過在網站注入惡意腳本立砸,使之在用戶的瀏覽器上運行,從而盜取用戶的信息如 cookie 等初茶。

XSS 的本質是因為網站沒有對惡意代碼進行過濾颗祝,與正常的代碼混合在一起了,瀏覽器沒有辦法分辨哪些腳本是可信的恼布,從而導致了惡意代碼的執(zhí)行螺戳。

XSS 一般分為存儲型、反射型和 DOM 型折汞。

存儲型指的是惡意代碼提交到了網站的數據庫中倔幼,當用戶請求數據的時候,服務器將其拼接為 HTML 后返回給了用戶爽待,從而導致了惡意代碼的執(zhí)行损同。

反射型指的是攻擊者構建了特殊的 URL翩腐,當服務器接收到請求后,從 URL 中獲取數據膏燃,拼接到 HTML 后返回茂卦,從而導致了惡意代碼的執(zhí)行。

DOM 型指的是攻擊者構建了特殊的 URL组哩,用戶打開網站后等龙,js 腳本從 URL 中獲取數據,從而導致了惡意代碼的執(zhí)行伶贰。

XSS 攻擊的預防可以從兩個方面入手蛛砰,一個是惡意代碼提交的時候,一個是瀏覽器執(zhí)行惡意代碼的時候黍衙。

對于第一個方面泥畅,如果我們對存入數據庫的數據都進行的轉義處理,但是一個數據可能在多個地方使用们豌,有的地方可能不需要轉義涯捻,由于我們沒有辦法判斷數據最后的使用場景,所以直接在輸入端進行惡意代碼的處理望迎,其實是不太可靠的障癌。

因此我們可以從瀏覽器的執(zhí)行來進行預防,一種是使用純前端的方式辩尊,不用服務器端拼接后返回涛浙。另一種是對需要插入到 HTML 中的代碼做好充分的轉義。對于 DOM 型的攻擊摄欲,主要是前端腳本的不可靠而造成的轿亮,我們對于數據獲取渲染和字符串拼接的時候應該對可能出現的惡意代碼情況進行判斷。

還有一些方式胸墙,比如使用 CSP 我注,CSP 的本質是建立一個白名單,告訴瀏覽器哪些外部資源可以加載和執(zhí)行迟隅,從而防止惡意代碼的注入攻擊但骨。

還可以對一些敏感信息進行保護,比如 cookie 使用 http-only 智袭,使得腳本無法獲取奔缠。也可以使用驗證碼,避免腳本偽裝成用戶執(zhí)行一些操作吼野。
詳細資料可以參考:
《前端安全系列(一):如何防止 XSS 攻擊校哎?》

094. 什么是 CSP?

CSP 指的是內容安全策略,它的本質是建立一個白名單闷哆,告訴瀏覽器哪些外部資源可以加載和執(zhí)行腰奋。我們只需要配置規(guī)則,如何攔截由瀏覽器自己來實現阳准。

通常有兩種方式來開啟 CSP氛堕,一種是設置 HTTP 首部中的 Content-Security-Policy,一種是設置 meta 標簽的方式 <meta
http-equiv="Content-Security-Policy">
詳細資料可以參考:
《內容安全策略(CSP)》
《前端面試之道》

095. 什么是 CSRF 攻擊野蝇?如何防范 CSRF 攻擊讼稚?

CSRF 攻擊指的是跨站請求偽造攻擊,攻擊者誘導用戶進入一個第三方網站绕沈,然后該網站向被攻擊網站發(fā)送跨站請求锐想。如果用戶在被
攻擊網站中保存了登錄狀態(tài),那么攻擊者就可以利用這個登錄狀態(tài)乍狐,繞過后臺的用戶驗證赠摇,冒充用戶向服務器執(zhí)行一些操作。

CSRF 攻擊的本質是利用了 cookie 會在同源請求中攜帶發(fā)送給服務器的特點浅蚪,以此來實現用戶的冒充藕帜。

一般的 CSRF 攻擊類型有三種:

第一種是 GET 類型的 CSRF 攻擊,比如在網站中的一個 img 標簽里構建一個請求惜傲,當用戶打開這個網站的時候就會自動發(fā)起提
交洽故。

第二種是 POST 類型的 CSRF 攻擊,比如說構建一個表單盗誊,然后隱藏它时甚,當用戶進入頁面時,自動提交這個表單哈踱。

第三種是鏈接類型的 CSRF 攻擊荒适,比如說在 a 標簽的 href 屬性里構建一個請求,然后誘導用戶去點擊开镣。

CSRF 可以用下面幾種方法來防護:

第一種是同源檢測的方法刀诬,服務器根據 http 請求頭中 origin 或者 referer 信息來判斷請求是否為允許訪問的站點,從而對請求進行過濾邪财。當 origin 或者 referer 信息都不存在的時候舅列,直接阻止。這種方式的缺點是有些情況下 referer 可以被偽造卧蜓。還有就是我們這種方法同時把搜索引擎的鏈接也給屏蔽了,所以一般網站會允許搜索引擎的頁面請求把敞,但是相應的頁面請求這種請求方式也可能被攻擊者給利用弥奸。

第二種方法是使用 CSRF Token 來進行驗證,服務器向用戶返回一個隨機數 Token 奋早,當網站再次發(fā)起請求時盛霎,在請求參數中加入服務器端返回的 token 赠橙,然后服務器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時愤炸,可能會被冒用的問題期揪,但是這種方法存在一個缺點就是,我們需要給網站中的所有請求都添加上這個 token规个,操作比較繁瑣凤薛。還有一個問題是一般不會只有一臺網站服務器,如果我們的請求經過負載平衡轉移到了其他的服務器诞仓,但是這個服務器的 session 中沒有保留這個 token 的話缤苫,就沒有辦法驗證了。這種情況我們可以通過改變 token 的構建方式來解決墅拭。

第三種方式使用雙重 Cookie 驗證的辦法活玲,服務器在用戶訪問網站頁面時,向請求域名注入一個Cookie谍婉,內容為隨機字符串舒憾,然后當用戶再次向服務器發(fā)送請求的時候,從 cookie 中取出這個字符串穗熬,添加到 URL 參數中镀迂,然后服務器通過對 cookie 中的數據和參數中的數據進行比較,來進行驗證死陆。使用這種方式是利用了攻擊者只能利用 cookie招拙,但是不能訪問獲取 cookie 的特點。并且這種方法比 CSRF Token 的方法更加方便措译,并且不涉及到分布式訪問的問題别凤。這種方法的缺點是如果網站存在 XSS 漏洞的,那么這種方式會失效领虹。同時這種方式不能做到子域名的隔離规哪。

第四種方式是使用在設置 cookie 屬性的時候設置 Samesite ,限制 cookie 不能作為被第三方使用塌衰,從而可以避免被攻擊者利用诉稍。Samesite 一共有兩種模式,一種是嚴格模式最疆,在嚴格模式下 cookie 在任何情況下都不可能作為第三方 Cookie 使用杯巨,在寬松模式下,cookie 可以被請求是 GET 請求努酸,且會發(fā)生頁面跳轉的請求所使用服爷。

096. 什么是 Samesite Cookie 屬性?

Samesite Cookie 表示同站 cookie,避免 cookie 被第三方所利用仍源。

將 Samesite 設為 strict 心褐,這種稱為嚴格模式,表示這個 cookie 在任何情況下都不可能作為第三方 cookie笼踩。

將 Samesite 設為 Lax 逗爹,這種模式稱為寬松模式,如果這個請求是個 GET 請求嚎于,并且這個請求改變了當前頁面或者打開了新的頁面掘而,那么這個 cookie 可以作為第三方 cookie,其余情況下都不能作為第三方 cookie匾旭。

使用這種方法的缺點是镣屹,因為它不支持子域,所以子域沒有辦法與主域共享登錄信息价涝,每次轉入子域的網站女蜈,都回重新登錄。還有一個問題就是它的兼容性不夠好色瘩。

097 什么是點擊劫持伪窖?如何防范點擊劫持?

點擊劫持是一種視覺欺騙的攻擊手段居兆,攻擊者將需要攻擊的網站通過 iframe 嵌套的方式嵌入自己的網頁中覆山,并將 iframe 設置為透明,在頁面中透出一個按鈕誘導用戶點擊泥栖。

我們可以在 http 相應頭中設置 X-FRAME-OPTIONS 來防御用 iframe 嵌套的點擊劫持攻擊簇宽。通過不同的值,可以規(guī)定頁面在特
定的一些情況才能作為 iframe 來使用魏割。
詳細資料可以參考:
《web 安全之--點擊劫持攻擊與防御技術簡介》

098. SQL 注入攻擊钞它?

SQL 注入攻擊指的是攻擊者在 HTTP 請求中注入惡意的 SQL 代碼遭垛,服務器使用參數構建數據庫 SQL 命令時操灿,惡意 SQL 被一起構
造锯仪,破壞原有 SQL 結構,并在數據庫中執(zhí)行趾盐,達到編寫程序時意料之外結果的攻擊行為庶喜。
詳細資料可以參考:
《Web 安全漏洞之 SQL 注入》
《如何防范常見的 Web 攻擊》

099. 什么是 MVVM幌蚊?比之 MVC 有什么區(qū)別?什么又是 MVP 溃卡?

MVC、MVP 和 MVVM 是三種常見的軟件架構設計模式蜒简,主要通過分離關注點的方式來組織代碼結構瘸羡,優(yōu)化我們的開發(fā)效率。

比如說我們實驗室在以前項目開發(fā)的時候搓茬,使用單頁應用時犹赖,往往一個路由頁面對應了一個腳本文件,所有的頁面邏輯都在一個腳本文件里卷仑。頁面的渲染峻村、數據的獲取,對用戶事件的響應所有的應用邏輯都混合在一起锡凝,這樣在開發(fā)簡單項目時粘昨,可能看不出什么問題,當時一旦項目變得復雜窜锯,那么整個文件就會變得冗長吞瞪,混亂翠勉,這樣對我們的項目開發(fā)和后期的項目維護是非常不利的迹栓。

MVC 通過分離 Model华坦、View 和 Controller 的方式來組織代碼結構犁跪。其中 View 負責頁面的顯示邏輯,Model 負責存儲頁面的業(yè)務數據乏矾,以及對相應數據的操作。并且 View 和 Model 應用了觀察者模式捷沸,當 Model 層發(fā)生改變的時候它會通知有關 View 層更新頁面。Controller 層是 View 層和 Model 層的紐帶,它主要負責用戶與應用的響應操作序仙,當用戶與頁面產生交互的時候,Co
ntroller 中的事件觸發(fā)器就開始工作了,通過調用 Model 層糙申,來完成對 Model 的修改,然后 Model 層再去通知 View 層更新扛邑。

MVP 模式與 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中我們使用觀察者模式跨琳,來實現當 Model 層數據發(fā)生變化的時候,通知 View 層的更新埠啃。這樣 View 層和 Model 層耦合在一起毅该,當項目邏輯變得復雜的時候巴碗,可能會造成代碼的混亂召噩,并且可能會對代碼的復用性造成一些問題。MVP 的模式通過使用 Presenter 來實現對 View 層和 Model 層的解耦。MVC 中的
Controller 只知道 Model 的接口疲恢,因此它沒有辦法控制 View 層的更新,MVP 模式中萎攒,View 層的接口暴露給了 Presenter 因此我們可以在 Presenter 中將 Model 的變化和 View 的變化綁定在一起,以此來實現 View 和 Model 的同步更新斯够。這樣就實現了對 View 和 Model 的解耦束亏,Presenter 還包含了其他的響應邏輯。

MVVM 模式中的 VM,指的是 ViewModel东跪,它和 MVP 的思想其實是相同的卤唉,不過它通過雙向的數據綁定,將 View 和 Model 的同步更新給自動化了熬的。當 Model 發(fā)生變化的時候理逊,ViewModel 就會自動更新橡伞;ViewModel 變化了盒揉,View 也會更新钮糖。這樣就將 Presenter 中的工作給自動化了胞谭。我了解過一點雙向數據綁定的原理,比如 vue 是通過使用數據劫持和發(fā)布訂閱者模式來實現的這一功
能鹤竭。

100. vue 雙向數據綁定原理牺蹄?

vue 通過使用雙向數據綁定,來實現了 View 和 Model 的同步更新腹躁。vue 的雙向數據綁定主要是通過使用數據劫持和發(fā)布訂閱者模式來實現的。

首先我們通過 Object.defineProperty() 方法來對 Model 數據各個屬性添加訪問器屬性憎账,以此來實現數據的劫持,因此當 Model 中的數據發(fā)生變化的時候锭吨,我們可以通過配置的 setter 和 getter 方法來實現對 View 層數據更新的通知。

數據在 html 模板中一共有兩種綁定情況,一種是使用 v-model 來對 value 值進行綁定,一種是作為文本綁定,在對模板引擎進行解析的過程中。

如果遇到元素節(jié)點,并且屬性值包含 v-model 的話,我們就從 Model 中去獲取 v-model 所對應的屬性的值蛾洛,并賦值給元素的 value 值拯啦。然后給這個元素設置一個監(jiān)聽事件,當 View 中元素的數據發(fā)生變化的時候觸發(fā)該事件后雷,通知 Model 中的對應的屬性的值進行更新。

如果遇到了綁定的文本節(jié)點,我們使用 Model 中對應的屬性的值來替換這個文本儒恋。對于文本節(jié)點的更新善绎,我們使用了發(fā)布訂閱者模式,屬性作為一個主題诫尽,我們?yōu)檫@個節(jié)點設置一個訂閱者對象禀酱,將這個訂閱者對象加入這個屬性主題的訂閱者列表中。當 Model 層數據發(fā)生改變的時候牧嫉,Model 作為發(fā)布者向主題發(fā)出通知剂跟,主題收到通知再向它的所有訂閱者推送,訂閱者收到通知后更改自己的數
據酣藻。
詳細資料可以參考:
《Vue.js 雙向綁定的實現原理》

101. Object.defineProperty 介紹曹洽?

Object.defineProperty 函數一共有三個參數,第一個參數是需要定義屬性的對象辽剧,第二個參數是需要定義的屬性送淆,第三個是該屬性描述符。

一個屬性的描述符有四個屬性怕轿,分別是 value 屬性的值偷崩,writable 屬性是否可寫,enumerable 屬性是否可枚舉撞羽,configurable 屬性是否可配置修改阐斜。
詳細資料可以參考:
《Object.defineProperty()》

102. 使用 Object.defineProperty() 來進行數據劫持有什么缺點?

有一些對屬性的操作放吩,使用這種方法無法攔截智听,比如說通過下標方式修改數組數據或者給對象新增屬性羽杰,vue 內部通過重寫函數解決了這個問題渡紫。在 Vue3.0 中已經不使用這種方式了,而是通過使用 Proxy 對對象進行代理考赛,從而實現數據劫持惕澎。使用 Proxy 的好處是它可以完美的監(jiān)聽到任何方式的數據改變,唯一的缺點是兼容性的問題颜骤,因為這是 ES6 的語法唧喉。

103. 什么是 Virtual DOM?為什么 Virtual DOM 比原生 DOM 快?

我對 Virtual DOM 的理解是八孝,

首先對我們將要插入到文檔中的 DOM 樹結構進行分析董朝,使用 js 對象將其表示出來,比如一個元素對象干跛,包含 TagName子姜、props 和 Children 這些屬性。然后我們將這個 js 對象樹給保存下來楼入,最后再將 DOM 片段插入到文檔中哥捕。

當頁面的狀態(tài)發(fā)生改變,我們需要對頁面的 DOM 的結構進行調整的時候嘉熊,我們首先根據變更的狀態(tài)遥赚,重新構建起一棵對象樹,然后將這棵新的對象樹和舊的對象樹進行比較阐肤,記錄下兩棵樹的的差異凫佛。

最后將記錄的有差異的地方應用到真正的 DOM 樹中去,這樣視圖就更新了泽腮。

我認為 Virtual DOM 這種方法對于我們需要有大量的 DOM 操作的時候御蒲,能夠很好的提高我們的操作效率,通過在操作前確定需要做的最小修改诊赊,盡可能的減少 DOM 操作帶來的重流和重繪的影響厚满。其實 Virtual DOM 并不一定比我們真實的操作 DOM 要快,這種方法的目的是為了提高我們開發(fā)時的可維護性碧磅,在任意的情況下碘箍,都能保證一個盡量小的性能消耗去進行操作。
詳細資料可以參考:
《Virtual DOM》
《理解 Virtual DOM》
《深度剖析:如何實現一個 Virtual DOM 算法》
《網上都說操作真實 DOM 慢鲸郊,但測試結果卻比 React 更快丰榴,為什么?》

104. 如何比較兩個 DOM 樹的差異秆撮?

兩個樹的完全 diff 算法的時間復雜度為 O(n^3) 四濒,但是在前端中,我們很少會跨層級的移動元素职辨,所以我們只需要比較同一層級的元素進行比較盗蟆,這樣就可以將算法的時間復雜度降低為 O(n)。

算法首先會對新舊兩棵樹進行一個深度優(yōu)先的遍歷舒裤,這樣每個節(jié)點都會有一個序號喳资。在深度遍歷的時候,每遍歷到一個節(jié)點腾供,我們就將這個節(jié)點和新的樹中的節(jié)點進行比較仆邓,如果有差異鲜滩,則將這個差異記錄到一個對象中。

在對列表元素進行對比的時候节值,由于 TagName 是重復的徙硅,所以我們不能使用這個來對比。我們需要給每一個子節(jié)點加上一個 key搞疗,列表對比的時候使用 key 來進行比較闷游,這樣我們才能夠復用老的 DOM 樹上的節(jié)點。

105. 什么是 requestAnimationFrame 贴汪?

詳細資料可以參考:
《你需要知道的 requestAnimationFrame》
《CSS3 動畫那么強脐往,requestAnimationFrame 還有毛線用?》

106. 談談你對 webpack 的看法

我當時使用 webpack 的一個最主要原因是為了簡化頁面依賴的管理扳埂,并且通過將其打包為一個文件來降低頁面加載時請求的資源
數业簿。

我認為 webpack 的主要原理是,它將所有的資源都看成是一個模塊阳懂,并且把頁面邏輯當作一個整體梅尤,通過一個給定的入口文件,webpack 從這個文件開始岩调,找到所有的依賴文件巷燥,將各個依賴文件模塊通過 loader 和 plugins 處理后,然后打包在一起号枕,最后輸出一個瀏覽器可識別的 JS 文件缰揪。

Webpack 具有四個核心的概念,分別是 Entry(入口)葱淳、Output(輸出)钝腺、loader 和 Plugins(插件)。

Entry 是 webpack 的入口起點赞厕,它指示 webpack 應該從哪個模塊開始著手艳狐,來作為其構建內部依賴圖的開始。

Output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的打包文件皿桑,也可指定打包文件的名稱毫目,默認位置為 ./dist。

loader 可以理解為 webpack 的編譯器诲侮,它使得 webpack 可以處理一些非 JavaScript 文件镀虐。在對 loader 進行配置的時候,test 屬性浆西,標志有哪些后綴的文件應該被處理粉私,是一個正則表達式顽腾。use 屬性近零,指定 test 類型的文件應該使用哪個 loader 進行預處理诺核。常用的 loader 有 css-loader、style-loader 等久信。

插件可以用于執(zhí)行范圍更廣的任務窖杀,包括打包、優(yōu)化裙士、壓縮入客、搭建服務器等等,要使用一個插件腿椎,一般是先使用 npm 包管理器進行安裝桌硫,然后在配置文件中引入,最后將其實例化后傳遞給 plugins 數組屬性啃炸。

使用 webpack 的確能夠提供我們對于項目的管理铆隘,但是它的缺點就是調試和配置起來太麻煩了。但現在 webpack4.0 的免配置一定程度上解決了這個問題南用。但是我感覺就是對我來說膀钠,就是一個黑盒,很多時候出現了問題裹虫,沒有辦法很好的定位肿嘲。
詳細資料可以參考:
《不聊 webpack 配置,來說說它的原理》
《前端工程化——構建工具選型:grunt筑公、gulp雳窟、webpack》
《淺入淺出 webpack》
《前端構建工具發(fā)展及其比較》

107. offsetWidth/offsetHeight,clientWidth/clientHeight 與 scrollWidth/scrollHeight 的區(qū)別?

clientWidth/clientHeight 返回的是元素的內部寬度匣屡,它的值只包含 content + padding涩拙,如果有滾動條,不包含滾動條耸采。
clientTop 返回的是上邊框的寬度兴泥。
clientLeft 返回的左邊框的寬度。

offsetWidth/offsetHeight 返回的是元素的布局寬度虾宇,它的值包含 content + padding + border 包含了滾動條搓彻。
offsetTop 返回的是當前元素相對于其 offsetParent 元素的頂部的距離。
offsetLeft 返回的是當前元素相對于其 offsetParent 元素的左部的距離嘱朽。

scrollWidth/scrollHeight 返回值包含 content + padding + 溢出內容的尺寸旭贬。
scrollTop 屬性返回的是一個元素的內容垂直滾動的像素數。
scrollLeft 屬性返回的是元素滾動條到元素左邊的距離搪泳。
詳細資料可以參考:
《最全的獲取元素寬高及位置的方法》
《用 Javascript 獲取頁面元素的位置》

108. 談一談你理解的函數式編程稀轨?

簡單說,"函數式編程"是一種"編程范式"(programming paradigm)岸军,也就是如何編寫程序的方法論奋刽。

它具有以下特性:閉包和高階函數瓦侮、惰性計算、遞歸佣谐、函數是"第一等公民"肚吏、只用"表達式"。
詳細資料可以參考:
《函數式編程初探》

109. 異步編程的實現方式狭魂?

相關資料:
回調函數
優(yōu)點:簡單罚攀、容易理解
缺點:不利于維護,代碼耦合高

事件監(jiān)聽(采用時間驅動模式雌澄,取決于某個事件是否發(fā)生):
優(yōu)點:容易理解斋泄,可以綁定多個事件,每個事件可以指定多個回調函數
缺點:事件驅動型镐牺,流程不夠清晰

發(fā)布/訂閱(觀察者模式)
類似于事件監(jiān)聽是己,但是可以通過‘消息中心’,了解現在有多少發(fā)布者任柜,多少訂閱者

Promise 對象
優(yōu)點:可以利用 then 方法卒废,進行鏈式寫法;可以書寫錯誤時的回調函數宙地;
缺點:編寫和理解摔认,相對比較難

Generator 函數
優(yōu)點:函數體內外的數據交換、錯誤處理機制
缺點:流程管理不方便

async 函數
優(yōu)點:內置執(zhí)行器宅粥、更好的語義参袱、更廣的適用性秽梅、返回的是 Promise、結構清晰朵诫。
缺點:錯誤處理機制
回答:
js 中的異步機制可以分為以下幾種:

第一種最常見的是使用回調函數的方式九默,使用回調函數的方式有一個缺點是幢竹,多個回調函數嵌套的時候會造成回調函數地獄,上下兩層的回調函數間的代碼耦合度太高乞旦,不利于代碼的可維護客峭。

第二種是 Promise 的方式抡柿,使用 Promise 的方式可以將嵌套的回調函數作為鏈式調用舔琅。但是使用這種方法,有時會造成多個 then 的鏈式調用洲劣,可能會造成代碼的語義不夠明確备蚓。

第三種是使用 generator 的方式课蔬,它可以在函數的執(zhí)行過程中,將函數的執(zhí)行權轉移出去郊尝,在函數外部我們還可以將執(zhí)行權轉移回來二跋。當我們遇到異步函數執(zhí)行的時候,將函數執(zhí)行權轉移出去流昏,當異步函數執(zhí)行完畢的時候我們再將執(zhí)行權給轉移回來扎即。因此我們在 generator 內部對于異步操作的方式,可以以同步的順序來書寫况凉。使用這種方式我們需要考慮的問題是何時將函數的控制權轉移回來谚鄙,因此我們需要有一個自動執(zhí)行 generator 的機制,比如說 co 模塊等方式來實現 generator 的自動執(zhí)行刁绒。

第四種是使用 async 函數的形式闷营,async 函數是 generator 和 promise 實現的一個自動執(zhí)行的語法糖,它內部自帶執(zhí)行器知市,當函數內部執(zhí)行到一個 await 語句的時候傻盟,如果語句返回一個 promise 對象,那么函數將會等待 promise 對象的狀態(tài)變?yōu)?resolve 后再繼續(xù)向下執(zhí)行嫂丙。因此我們可以將異步邏輯莫杈,轉化為同步的順序來書寫,并且這個函數可以自動執(zhí)行奢入。

110. Js 動畫與 CSS 動畫區(qū)別及相應實現

CSS3 的動畫的優(yōu)點

在性能上會稍微好一些筝闹,瀏覽器會對 CSS3 的動畫做一些優(yōu)化
代碼相對簡單

缺點

在動畫控制上不夠靈活
兼容性不好

JavaScript 的動畫正好彌補了這兩個缺點,控制能力很強腥光,可以單幀的控制关顷、變換,同時寫得好完全可以兼容 IE6武福,并且功能強大议双。對于一些復雜控制的動畫,使用 javascript 會比較靠譜捉片。而在實現一些小的交互動效的時候平痰,就多考慮考慮 CSS 吧

111. get 請求傳參長度的誤區(qū)

誤區(qū):我們經常說 get 請求參數的大小存在限制,而 post 請求的參數大小是無限制的伍纫。

實際上 HTTP 協議從未規(guī)定 GET/POST 的請求長度限制是多少宗雇。對 get 請求參數的限制是來源與瀏覽器或web 服務器,瀏覽器或 web 服務器限制了 url 的長度莹规。為了明確這個概念赔蒲,我們必須再次強調下面幾點:
1.HTTP 協議未規(guī)定 GET 和 POST 的長度限制
2.GET 的最大長度顯示是因為瀏覽器和 web 服務器限制了 URI 的長度
3.不同的瀏覽器和 WEB 服務器,限制的最大長度不一樣
4.要支持 IE,則最大長度為 2083byte舞虱,若只支持 Chrome欢际,則最大長度 8182byte

112. URL 和 URI 的區(qū)別?

URI: Uniform Resource Identifier 指的是統(tǒng)一資源標識符
URL: Uniform Resource Location 指的是統(tǒng)一資源定位符
URN: Universal Resource Name 指的是統(tǒng)一資源名稱

URI 指的是統(tǒng)一資源標識符矾兜,用唯一的標識來確定一個資源损趋,它是一種抽象的定義,也就是說椅寺,不管使用什么方法來定義浑槽,只要能唯一的標識一個資源,就可以稱為 URI配并。

URL 指的是統(tǒng)一資源定位符括荡,URN 指的是統(tǒng)一資源名稱高镐。URL 和 URN 是 URI 的子集溉旋,URL 可以理解為使用地址來標識資源,URN 可以理解為使用名稱來標識資源嫉髓。
詳細資料可以參考:
《HTTP 協議中 URI 和 URL 有什么區(qū)別观腊?》
《你知道 URL、URI 和 URN 三者之間的區(qū)別嗎算行?》
《URI梧油、URL 和 URN 的區(qū)別》

113. get 和 post 請求在緩存方面的區(qū)別

相關知識點:
get 請求類似于查找的過程,用戶獲取數據州邢,可以不用每次都與數據庫連接儡陨,所以可以使用緩存。

post 不同量淌,post 做的一般是修改和刪除的工作骗村,所以必須與數據庫交互,所以不能使用緩存呀枢。因此 get 請求適合于請求緩存胚股。
回答:
緩存一般只適用于那些不會更新服務端數據的請求。一般 get 請求都是查找請求裙秋,不會對服務器資源數據造成修改琅拌,而 post 請求一般都會對服務器數據造成修改,所以摘刑,一般會對 get 請求進行緩存进宝,很少會對 post 請求進行緩存。
詳細資料可以參考:
《HTML 關于 post 和 get 的區(qū)別以及緩存問題的理解》

114. 圖片的懶加載和預加載

相關知識點:
預加載:提前加載圖片枷恕,當用戶需要查看時可直接從本地緩存中渲染即彪。

懶加載:懶加載的主要目的是作為服務器前端的優(yōu)化,減少請求數或延遲請求數。

兩種技術的本質:兩者的行為是相反的隶校,一個是提前加載漏益,一個是遲緩甚至不加載。懶加載對服務器前端有一定的緩解壓力作用深胳,預加載則會增加服務器前端壓力绰疤。
回答:
懶加載也叫延遲加載,指的是在長網頁中延遲加載圖片的時機舞终,當用戶需要訪問時轻庆,再去加載,這樣可以提高網站的首屏加載速度敛劝,提升用戶的體驗余爆,并且可以減少服務器的壓力。它適用于圖片很多夸盟,頁面很長的電商網站的場景蛾方。懶加載的實現原理是,將頁面上的圖片的 src 屬性設置為空字符串上陕,將圖片的真實路徑保存在一個自定義屬性中桩砰,當頁面滾動的時候,進行判斷释簿,如果圖片進入頁面可視區(qū)域內亚隅,則從自定義屬性中取出真實路徑賦值給圖片的 src 屬性,以此來實現圖片的延遲加載庶溶。

預加載指的是將所需的資源提前請求加載到本地煮纵,這樣后面在需要用到時就直接從緩存取資源。通過預加載能夠減少用戶的等待時間偏螺,提高用戶的體驗行疏。我了解的預加載的最常用的方式是使用 js 中的 image 對象,通過為 image 對象來設置 scr 屬性砖茸,來實現圖片的預加載隘擎。

這兩種方式都是提高網頁性能的方式,兩者主要區(qū)別是一個是提前加載凉夯,一個是遲緩甚至不加載货葬。懶加載對服務器前端有一定的緩解壓力作用,預加載則會增加服務器前端壓力劲够。
詳細資料可以參考:
《懶加載和預加載》
《網頁圖片加載優(yōu)化方案》
《基于用戶行為的圖片等資源預加載》

115. mouseover 和 mouseenter 的區(qū)別震桶?

當鼠標移動到元素上時就會觸發(fā) mouseenter 事件,類似 mouseover征绎,它們兩者之間的差別是 mouseenter 不會冒泡蹲姐。

由于 mouseenter 不支持事件冒泡磨取,導致在一個元素的子元素上進入或離開的時候會觸發(fā)其 mouseover 和 mouseout 事件,但是卻不會觸發(fā) mouseenter 和 mouseleave 事件柴墩。
詳細資料可以參考:
《mouseenter 與 mouseover 為何這般糾纏不清忙厌?》

116. js 拖拽功能的實現

相關知識點:
首先是三個事件,分別是 mousedown江咳,mousemove逢净,mouseup
當鼠標點擊按下的時候,需要一個 tag 標識此時已經按下歼指,可以執(zhí)行 mousemove 里面的具體方法爹土。
clientX,clientY 標識的是鼠標的坐標踩身,分別標識橫坐標和縱坐標胀茵,并且我們用 offsetX 和 offsetY 來表示
元素的元素的初始坐標,移動的舉例應該是:
鼠標移動時候的坐標-鼠標按下去時候的坐標挟阻。
也就是說定位信息為:
鼠標移動時候的坐標-鼠標按下去時候的坐標+元素初始情況下的 offetLeft.
回答:
一個元素的拖拽過程琼娘,我們可以分為三個步驟,第一步是鼠標按下目標元素赁濒,第二步是鼠標保持按下的狀態(tài)移動鼠標轨奄,第三步是鼠
標抬起孟害,拖拽過程結束拒炎。

這三步分別對應了三個事件,mousedown 事件挨务,mousemove 事件和 mouseup 事件击你。只有在鼠標按下的狀態(tài)移動鼠標我們才會
執(zhí)行拖拽事件,因此我們需要在 mousedown 事件中設置一個狀態(tài)來標識鼠標已經按下谎柄,然后在 mouseup 事件中再取消這個狀
態(tài)丁侄。在 mousedown 事件中我們首先應該判斷,目標元素是否為拖拽元素朝巫,如果是拖拽元素鸿摇,我們就設置狀態(tài)并且保存這個時候鼠
標的位置。然后在 mousemove 事件中劈猿,我們通過判斷鼠標現在的位置和以前位置的相對移動民褂,來確定拖拽元素在移動中的坐標摹蘑。
最后 mouseup 事件觸發(fā)后,清除狀態(tài),結束拖拽事件辽幌。
詳細資料可以參考:
《原生 js 實現拖拽功能基本思路》

117. 為什么使用 setTimeout 實現 setInterval?怎么模擬刻盐?

相關知識點:

// 思路是使用遞歸函數诸尽,不斷地去執(zhí)行 setTimeout 從而達到 setInterval 的效果

function mySetInterval(fn, timeout) {
  // 控制器,控制定時器是否繼續(xù)執(zhí)行
  var timer = {
    flag: true
  };

  // 設置遞歸函數,模擬定時器執(zhí)行请祖。
  function interval() {
    if (timer.flag) {
      fn();
      setTimeout(interval, timeout);
    }
  }

  // 啟動定時器
  setTimeout(interval, timeout);

  // 返回控制器
  return timer;
}

回答:
setInterval 的作用是每隔一段指定時間執(zhí)行一個函數订歪,但是這個執(zhí)行不是真的到了時間立即執(zhí)行,它真正的作用是每隔一段時間將事件加入事件隊列中去肆捕,只有當當前的執(zhí)行棧為空的時候陌粹,才能去從事件隊列中取出事件執(zhí)行。所以可能會出現這樣的情況福压,就是當前執(zhí)行棧執(zhí)行的時間很長掏秩,導致事件隊列里邊積累多個定時器加入的事件,當執(zhí)行棧結束的時候荆姆,這些事件會依次執(zhí)行蒙幻,因此就不能到間隔一段時間執(zhí)行的效果。

針對 setInterval 的這個缺點胆筒,我們可以使用 setTimeout 遞歸調用來模擬 setInterval邮破,這樣我們就確保了只有一個事件結束了,我們才會觸發(fā)下一個定時器事件仆救,這樣解決了 setInterval 的問題抒和。
詳細資料可以參考:
《用 setTimeout 實現 setInterval》
《setInterval 有什么缺點?》

118. let 和 const 的注意點彤蔽?

1.聲明的變量只在聲明時的代碼塊內有效
2.不存在聲明提升
3.存在暫時性死區(qū)摧莽,如果在變量聲明前使用,會報錯
4.不允許重復聲明顿痪,重復聲明會報錯

119. 什么是 rest 參數镊辕?

rest 參數(形式為...變量名),用于獲取函數的多余參數蚁袭。

120. 什么是尾調用征懈,使用尾調用有什么好處?

尾調用指的是函數的最后一步調用另一個函數揩悄。我們代碼執(zhí)行是基于執(zhí)行棧的卖哎,所以當我們在一個函數里調用另一個函數時,我們會保留當前的執(zhí)行上下文删性,然后再新建另外一個執(zhí)行上下文加入棧中亏娜。使用尾調用的話,因為已經是函數的最后一步镇匀,所以這個時候我們可以不必再保留當前的執(zhí)行上下文照藻,從而節(jié)省了內存,這就是尾調用優(yōu)化汗侵。但是 ES6 的尾調用優(yōu)化只在嚴格模式下開啟幸缕,正常模式是無效的群发。

121. Symbol 類型的注意點?

1.Symbol 函數前不能使用 new 命令发乔,否則會報錯熟妓。
2.Symbol 函數可以接受一個字符串作為參數,表示對 Symbol 實例的描述栏尚,主要是為了在控制臺顯示起愈,或者轉為字符串時,比較容易區(qū)分译仗。
3.Symbol 作為屬性名抬虽,該屬性不會出現在 for...in、for...of 循環(huán)中纵菌,也不會被 Object.keys()阐污、Object.getOwnPropertyNames()、JSON.stringify() 返回咱圆。
4.Object.getOwnPropertySymbols 方法返回一個數組笛辟,成員是當前對象的所有用作屬性名的 Symbol 值。
5.Symbol.for 接受一個字符串作為參數序苏,然后搜索有沒有以該參數作為名稱的 Symbol 值手幢。如果有,就返回這個 Symbol 值忱详,否則就新建并返回一個以該字符串為名稱的 Symbol 值围来。
6.Symbol.keyFor 方法返回一個已登記的 Symbol 類型值的 key。

122. Set 和 WeakSet 結構踱阿?

1.ES6 提供了新的數據結構 Set管钳。它類似于數組钦铁,但是成員的值都是唯一的软舌,沒有重復的值。
2.WeakSet 結構與 Set 類似牛曹,也是不重復的值的集合佛点。但是 WeakSet 的成員只能是對象,而不能是其他類型的值黎比。WeakSet 中的對象都是弱引用超营,即垃圾回收機制不考慮 WeakSet 對該對象的引用,

123. Map 和 WeakMap 結構阅虫?

1.Map 數據結構演闭。它類似于對象,也是鍵值對的集合颓帝,但是“鍵”的范圍不限于字符串米碰,各種類型的值(包括對象)都可以當作鍵窝革。
2.WeakMap 結構與 Map 結構類似,也是用于生成鍵值對的集合吕座。但是 WeakMap 只接受對象作為鍵名( null 除外)虐译,不接受其他類型的值作為鍵名。而且 WeakMap 的鍵名所指向的對象吴趴,不計入垃圾回收機制漆诽。

124. 什么是 Proxy ?

Proxy 用于修改某些操作的默認行為锣枝,等同于在語言層面做出修改厢拭,所以屬于一種“元編程”,即對編程語言進行編程撇叁。

Proxy 可以理解成蚪腐,在目標對象之前架設一層“攔截”,外界對該對象的訪問税朴,都必須先通過這層攔截回季,因此提供了一種機制,可以對外界的訪問進行過濾和改寫正林。Proxy 這個詞的原意是代理泡一,用在這里表示由它來“代理”某些操作,可以譯為“代理器”觅廓。

125. Reflect 對象創(chuàng)建目的鼻忠?

1.將 Object 對象的一些明顯屬于語言內部的方法(比如 Object.defineProperty,放到 Reflect 對象上杈绸。
2.修改某些 Object 方法的返回結果帖蔓,讓其變得更合理。
3.讓 Object 操作都變成函數行為瞳脓。
4.Reflect 對象的方法與 Proxy 對象的方法一一對應塑娇,只要是 Proxy 對象的方法,就能在 Reflect 對象上找到對應的方法劫侧。這就讓 Proxy 對象可以方便地調用對應的 Reflect 方法埋酬,完成默認行為,作為修改行為的基礎烧栋。也就是說写妥,不管 Proxy 怎么修改默認行為,你總可以在 Reflect 上獲取默認行為审姓。

126. require 模塊引入的查找方式珍特?

當 Node 遇到 require(X) 時,按下面的順序處理魔吐。

(1)如果 X 是內置模塊(比如 require('http'))
  a. 返回該模塊扎筒。
  b. 不再繼續(xù)執(zhí)行呼猪。

(2)如果 X 以 "./" 或者 "/" 或者 "../" 開頭
  a. 根據 X 所在的父模塊,確定 X 的絕對路徑砸琅。
  b. 將 X 當成文件宋距,依次查找下面文件,只要其中有一個存在症脂,就返回該文件谚赎,不再繼續(xù)執(zhí)行。
X
X.js
X.json
X.node

c. 將 X 當成目錄诱篷,依次查找下面文件壶唤,只要其中有一個存在,就返回該文件棕所,不再繼續(xù)執(zhí)行闸盔。
X/package.json(main字段)
X/index.js
X/index.json
X/index.node

(3)如果 X 不帶路徑
  a. 根據 X 所在的父模塊,確定 X 可能的安裝目錄琳省。
  b. 依次在每個目錄中迎吵,將 X 當成文件名或目錄名加載。

(4)拋出 "not found"
詳細資料可以參考:
《require() 源碼解讀》

127. 什么是 Promise 對象针贬,什么是 Promises/A+ 規(guī)范击费?

Promise 對象是異步編程的一種解決方案,最早由社區(qū)提出桦他。Promises/A+ 規(guī)范是 JavaScript Promise 的標準蔫巩,規(guī)定了一個 Promise 所必須具有的特性。

Promise 是一個構造函數快压,接收一個函數作為參數圆仔,返回一個 Promise 實例。一個 Promise 實例有三種狀態(tài)蔫劣,分別是 pending坪郭、resolved 和 rejected,分別代表了進行中拦宣、已成功和已失敗截粗。實例的狀態(tài)只能由 pending 轉變 resolved 或者 rejected 狀態(tài),并且狀態(tài)一經改變鸵隧,就凝固了,無法再被改變了意推。狀態(tài)的改變是通過 resolve() 和 reject() 函數來實現的豆瘫,我們
可以在異步操作結束后調用這兩個函數改變 Promise 實例的狀態(tài),它的原型上定義了一個 then 方法菊值,使用這個 then 方法可以為兩個狀態(tài)的改變注冊回調函數外驱。這個回調函數屬于微任務育灸,會在本輪事件循環(huán)的末尾執(zhí)行。
詳細資料可以參考:
《Promises/A+ 規(guī)范》
《Promise》

128. 手寫一個 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化狀態(tài)
  var self = this;

  // 初始化狀態(tài)
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 傳入的值
  this.value = null;

  // 用于保存 resolve 的回調函數
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回調函數
  this.rejectedCallbacks = [];

  // 狀態(tài)轉變?yōu)?resolved 方法
  function resolve(value) {
    // 判斷傳入元素是否為 Promise 值昵宇,如果是磅崭,則狀態(tài)改變必須等待前一個狀態(tài)改變后再進行改變
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
    setTimeout(() => {
      // 只有狀態(tài)為 pending 時才能轉變,
      if (self.state === PENDING) {
        // 修改狀態(tài)
        self.state = RESOLVED;

        // 設置傳入的值
        self.value = value;

        // 執(zhí)行回調函數
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 狀態(tài)轉變?yōu)?rejected 方法
  function reject(value) {
    // 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
    setTimeout(() => {
      // 只有狀態(tài)為 pending 時才能轉變
      if (self.state === PENDING) {
        // 修改狀態(tài)
        self.state = REJECTED;

        // 設置傳入的值
        self.value = value;

        // 執(zhí)行回調函數
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 將兩個方法傳入函數執(zhí)行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到錯誤時瓦哎,捕獲錯誤砸喻,執(zhí)行 reject 函數
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };

  // 如果是等待狀態(tài)蒋譬,則將函數加入對應列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果狀態(tài)已經凝固割岛,則直接執(zhí)行對應狀態(tài)的函數

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

129. 如何檢測瀏覽器所支持的最小字體大小犯助?

用 JS 設置 DOM 的字體為某一個值癣漆,然后再取出來,如果值設置成功剂买,就說明支持惠爽。

130. 怎么做 JS 代碼 Error 統(tǒng)計?

error 統(tǒng)計使用瀏覽器的 window.error 事件瞬哼。

131 單例模式模式是什么疆股?

單例模式保證了全局只有一個實例來被訪問。比如說常用的如彈框組件的實現和全局狀態(tài)的實現倒槐。

132. 策略模式是什么旬痹?

策略模式主要是用來將方法的實現和方法的調用分離開,外部通過不同的參數可以調用不同的策略讨越。我主要在 MVP 模式解耦的時候
用來將視圖層的方法定義和方法調用分離两残。

133.代理模式是什么?

代理模式是為一個對象提供一個代用品或占位符把跨,以便控制對它的訪問人弓。比如說常見的事件代理。

134. 中介者模式是什么着逐?

中介者模式指的是崔赌,多個對象通過一個中介者進行交流,而不是直接進行交流耸别,這樣能夠將通信的各個對象解耦健芭。

135. 適配器模式是什么?

適配器用來解決兩個接口不兼容的情況秀姐,不需要改變已有的接口慈迈,通過包裝一層的方式實現兩個接口的正常協作。假如我們需要一種
新的接口返回方式省有,但是老的接口由于在太多地方已經使用了痒留,不能隨意更改谴麦,這個時候就可以使用適配器模式。比如我們需要一種
自定義的時間返回格式伸头,但是我們又不能對 js 時間格式化的接口進行修改匾效,這個時候就可以使用適配器模式。
更多關于設計模式的資料可以參考:
《前端面試之道》
《JavaScript 設計模式》
《JavaScript 中常見設計模式整理》

136.觀察者模式和發(fā)布訂閱模式有什么不同恤磷?

發(fā)布訂閱模式其實屬于廣義上的觀察者模式

在觀察者模式中面哼,觀察者需要直接訂閱目標事件。在目標發(fā)出內容改變的事件后碗殷,直接接收事件并作出響應精绎。

而在發(fā)布訂閱模式中,發(fā)布者和訂閱者之間多了一個調度中心锌妻。調度中心一方面從發(fā)布者接收事件代乃,另一方面向訂閱者發(fā)布事件,訂閱者需要在調度中心中訂閱事件仿粹。通過調度中心實現了發(fā)布者和訂閱者關系的解耦搁吓。使用發(fā)布訂閱者模式更利于我們代碼的可維護性。
詳細資料可以參考:
《觀察者模式和發(fā)布訂閱模式有什么不同吭历?》

137. Vue 的生命周期是什么堕仔?

Vue 的生命周期指的是組件從創(chuàng)建到銷毀的一系列的過程,被稱為 Vue 的生命周期晌区。通過提供的 Vue 在生命周期各個階段的鉤子函數摩骨,我們可以很好的在 Vue 的各個生命階段實現一些操作。

138. Vue 的各個生命階段是什么朗若?

Vue 一共有8個生命階段恼五,分別是創(chuàng)建前、創(chuàng)建后哭懈、加載前灾馒、加載后、更新前遣总、更新后睬罗、銷毀前和銷毀后,每個階段對應了一個生命周期的鉤子函數旭斥。

(1)beforeCreate 鉤子函數容达,在實例初始化之后,在數據監(jiān)聽和事件配置之前觸發(fā)琉预。因此在這個事件中我們是獲取不到 data 數據的董饰。

(2)created 鉤子函數,在實例創(chuàng)建完成后觸發(fā)圆米,此時可以訪問 data卒暂、methods 等屬性。但這個時候組件還沒有被掛載到頁面中去娄帖,所以這個時候訪問不到 $el 屬性也祠。一般我們可以在這個函數中進行一些頁面初始化的工作,比如通過 ajax 請求數據來對頁面進行初始化近速。

(3)beforeMount 鉤子函數诈嘿,在組件被掛載到頁面之前觸發(fā)。在 beforeMount 之前削葱,會找到對應的 template奖亚,并編譯成 render 函數。

(4)mounted 鉤子函數析砸,在組件掛載到頁面之后觸發(fā)昔字。此時可以通過 DOM API 獲取到頁面中的 DOM 元素。

(5)beforeUpdate 鉤子函數首繁,在響應式數據更新時觸發(fā)作郭,發(fā)生在虛擬 DOM 重新渲染和打補丁之前,這個時候我們可以對可能會被移除的元素做一些操作弦疮,比如移除事件監(jiān)聽器夹攒。

(6)updated 鉤子函數,虛擬 DOM 重新渲染和打補丁之后調用胁塞。

(7)beforeDestroy 鉤子函數咏尝,在實例銷毀之前調用。一般在這一步我們可以銷毀定時器啸罢、解綁全局事件等编检。

(8)destroyed 鉤子函數,在實例銷毀之后調用伺糠,調用后蒙谓,Vue 實例中的所有東西都會解除綁定,所有的事件監(jiān)聽器會被移除训桶,所有的子實例也會被銷毀累驮。

當我們使用 keep-alive 的時候,還有兩個鉤子函數舵揭,分別是 activated 和 deactivated 谤专。用 keep-alive 包裹的組件在切換時不會進行銷毀,而是緩存到內存中并執(zhí)行 deactivated 鉤子函數午绳,命中緩存渲染后會執(zhí)行 actived 鉤子函數置侍。
詳細資料可以參考:
《vue 生命周期深入》
《Vue 實例》

139. Vue 組件間的參數傳遞方式?

(1)父子組件間通信

第一種方法是子組件通過 props 屬性來接受父組件的數據,然后父組件在子組件上注冊監(jiān)聽事件蜡坊,子組件通過 emit 觸發(fā)事
件來向父組件發(fā)送數據杠输。

第二種是通過 ref 屬性給子組件設置一個名字。父組件通過 refs 組件名來獲得子組件秕衙,子組件通過parent 獲得父組
件蠢甲,這樣也可以實現通信。

第三種是使用 provider/inject据忘,在父組件中通過 provider 提供變量鹦牛,在子組件中通過 inject 來將變量注入到組件
中。不論子組件有多深勇吊,只要調用了 inject 那么就可以注入 provider 中的數據曼追。

(2)兄弟組件間通信

第一種是使用 eventBus 的方法,它的本質是通過創(chuàng)建一個空的 Vue 實例來作為消息傳遞的對象汉规,通信的組件引入這個實
例礼殊,通信的組件通過在這個實例上監(jiān)聽和觸發(fā)事件,來實現消息的傳遞鲫忍。

第二種是通過 parent.refs 來獲取到兄弟組件膏燕,也可以進行通信。

(3)任意組件之間

使用 eventBus 悟民,其實就是創(chuàng)建一個事件中心坝辫,相當于中轉站,可以用它來傳遞事件和接收事件射亏。

如果業(yè)務邏輯復雜近忙,很多組件之間需要同時處理一些公共的數據,這個時候采用上面這一些方法可能不利于項目的維護智润。這個時候
可以使用 vuex 及舍,vuex 的思想就是將這一些公共的數據抽離出來,將它作為一個全局的變量來管理窟绷,然后其他組件就可以對這個
公共數據進行讀寫操作锯玛,這樣達到了解耦的目的。
詳細資料可以參考:
《VUE 組件之間數據傳遞全集》

140. computed 和 watch 的差異攘残?

(1)computed 是計算一個新的屬性,并將該屬性掛載到 Vue 實例上为狸,而 watch 是監(jiān)聽已經存在且已掛載到 Vue 實例上的數據鲫竞,所以用 watch 同樣可以監(jiān)聽 computed 計算屬性的變化。

(2)computed 本質是一個惰性求值的觀察者负敏,具有緩存性贡茅,只有當依賴變化后秘蛇,第一次訪問 computed 屬性其做,才會計算新的值。而 watch 則是當數據發(fā)生變化便會調用執(zhí)行函數赁还。

(3)從使用場景上說妖泄,computed 適用一個數據被多個數據影響,而 watch 適用一個數據影響多個數據艘策。
詳細資料可以參考:
《做面試的不倒翁:淺談 Vue 中 computed 實現原理》
《深入理解 Vue 的 watch 實現原理及其實現方式》

141. vue-router 中的導航鉤子函數

(1)全局的鉤子函數 beforeEach 和 afterEach

beforeEach 有三個參數蹈胡,to 代表要進入的路由對象,from 代表離開的路由對象朋蔫。next 是一個必須要執(zhí)行的函數罚渐,如果不傳參數,那就執(zhí)行下一個鉤子函數驯妄,如果傳入 false荷并,則終止跳轉,如果傳入一個路徑青扔,則導航到對應的路由源织,如果傳入 error ,則導航終止微猖,error 傳入錯誤的監(jiān)聽函數谈息。

(2)單個路由獨享的鉤子函數 beforeEnter,它是在路由配置上直接進行定義的凛剥。

(3)組件內的導航鉤子主要有這三種:beforeRouteEnter侠仇、beforeRouteUpdate、beforeRouteLeave犁珠。它們是直接在路由組
件內部直接進行定義的逻炊。
詳細資料可以參考:
《導航守衛(wèi)》

142. route與router 的區(qū)別?

route 是“路由信息對象”盲憎,包括 path嗅骄,params,hash饼疙,query溺森,fullPath慕爬,matched,name 等路由信息參數屏积。而router 是“路由實例”對象包括了路由的跳轉方法医窿,鉤子函數等。

143. vue 常用的修飾符炊林?

.prevent: 提交事件不再重載頁面姥卢;.stop: 阻止單擊事件冒泡;.self: 當事件發(fā)生在該元素本身而不是子元素的時候會觸發(fā)渣聚;

144. vue 中 key 值的作用独榴?

vue 中 key 值的作用可以分為兩種情況來考慮。

第一種情況是 v-if 中使用 key奕枝。由于 Vue 會盡可能高效地渲染元素棺榔,通常會復用已有元素而不是從頭開始渲染。因此當我們使用 v-if 來實現元素切換的時候隘道,如果切換前后含有相同類型的元素症歇,那么這個元素就會被復用。如果是相同的 input 元素谭梗,那么切換前后用戶的輸入不會被清除掉忘晤,這樣是不符合需求的。因此我們可以通過使用 key 來唯一的標識一個元素激捏,這個情況下设塔,使用 key 的元素不會被復用。這個時候 key 的作用是用來標識一個獨立的元素缩幸。

第二種情況是 v-for 中使用 key壹置。用 v-for 更新已渲染過的元素列表時,它默認使用“就地復用”的策略表谊。如果數據項的順序發(fā)生了改變钞护,Vue 不會移動 DOM 元素來匹配數據項的順序,而是簡單復用此處的每個元素爆办。因此通過為每個列表項提供一個 key 值难咕,來以便 Vue 跟蹤元素的身份,從而高效的實現復用距辆。這個時候 key 的作用是為了高效的更新渲染虛擬 DOM余佃。
詳細資料可以參考:
《Vue 面試中,經常會被問到的面試題 Vue 知識點整理》
《Vue2.0 v-for 中 :key 到底有什么用跨算?》
《vue 中 key 的作用》

145. computed 和 watch 區(qū)別爆土?

computed 是計算屬性,依賴其他屬性計算值诸蚕,并且 computed 的值有緩存步势,只有當計算值變化才會返回內容家浇。

watch 監(jiān)聽到值的變化就會執(zhí)行回調瓤的,在回調中可以進行一些邏輯操作凡恍。

146 keep-alive 組件有什么作用润脸?

如果你需要在組件切換的時候,保存一些組件的狀態(tài)防止多次渲染倔矾,就可以使用 keep-alive 組件包裹需要保存的組件妄均。

147. vue 中 mixin 和 mixins 區(qū)別?

mixin 用于全局混入哪自,會影響到每個組件實例丰包。

mixins 應該是我們最常使用的擴展組件的方式了。如果多個組件中有相同的業(yè)務邏輯提陶,就可以將這些邏輯剝離出來烫沙,通過 mixins 混入代碼,比如上拉下拉加載數據這種邏輯等等隙笆。另外需要注意的是 mixins 混入的鉤子函數會先于組件內的鉤子函數執(zhí)行,并且在遇到同名選項的時候也會有選擇性的進行合并
詳細資料可以參考:
《前端面試之道》
《混入》

148. 開發(fā)中常用的幾種 Content-Type 升筏?

(1)application/x-www-form-urlencoded

瀏覽器的原生 form 表單撑柔,如果不設置 enctype 屬性,那么最終就會以 application/x-www-form-urlencoded 方式提交數據您访。該種方式提交的數據放在 body 里面铅忿,數據按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL
轉碼灵汪。

(2)multipart/form-data

該種方式也是一個常見的 POST 提交方式檀训,通常表單上傳文件時使用該種方式。

(3)application/json

告訴服務器消息主體是序列化后的 JSON 字符串享言。

(4)text/xml

該種方式主要用來提交 XML 格式的數據峻凫。
詳細資料可以參考:
《常用的幾種 Content-Type》

149. 如何封裝一個 javascript 的類型判斷函數?

function getType(value) {
  // 判斷數據是 null 的情況
  if (value === null) {
    return value + "";
  }

  // 判斷數據是引用類型的情況
  if (typeof value === "object") {
    let valueClass = Object.prototype.toString.call(value),
      type = valueClass.split(" ")[1].split("");

    type.pop();

    return type.join("").toLowerCase();
  } else {
    // 判斷數據是基本數據類型的情況和函數的情況
    return typeof value;
  }
}

151.使用閉包實現每隔一秒打印 1,2,3,4

// 使用閉包實現
for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}

// 使用 let 塊級作用域

for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}
####152.手寫一個 jsonp

function jsonp(url, params, callback) {
  // 判斷是否含有參數
  let queryString = url.indexOf("?") === "-1" ? "?" : "&";

  // 添加參數
  for (var k in params) {
    if (params.hasOwnProperty(k)) {
      queryString += k + "=" + params[k] + "&";
    }
  }

  // 處理回調函數名
  let random = Math.random()
      .toString()
      .replace(".", ""),
    callbackName = "myJsonp" + random;

  // 添加回調函數
  queryString += "callback=" + callbackName;

  // 構建請求
  let scriptNode = document.createElement("script");
  scriptNode.src = url + queryString;

  window[callbackName] = function() {
    // 調用回調函數
    callback(...arguments);

    // 刪除這個引入的腳本
    document.getElementsByTagName("head")[0].removeChild(scriptNode);
  };

  // 發(fā)起請求
  document.getElementsByTagName("head")[0].appendChild(scriptNode);
}

153.手寫一個觀察者模式览露?

var events = (function() {
  var topics = {};

  return {
    // 注冊監(jiān)聽函數
    subscribe: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
      topics[topic].push(handler);
    },

    // 發(fā)布事件荧琼,觸發(fā)觀察者回調事件
    publish: function(topic, info) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic].forEach(function(handler) {
          handler(info);
        });
      }
    },

    // 移除主題的一個觀察者的回調事件
    remove: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) return;

      var handlerIndex = -1;
      topics[topic].forEach(function(item, index) {
        if (item === handler) {
          handlerIndex = index;
        }
      });

      if (handlerIndex >= 0) {
        topics[topic].splice(handlerIndex, 1);
      }
    },

    // 移除主題的所有觀察者的回調事件
    removeAll: function(topic) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
    }
  };
})();

154. EventEmitter 實現

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    let callbacks = this.events[event] || [];
    callbacks.push(callback);
    this.events[event] = callbacks;

    return this;
  }

  off(event, callback) {
    let callbacks = this.events[event];
    this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

    return this;
  }

  emit(event, ...args) {
    let callbacks = this.events[event];
    callbacks.forEach(fn => {
      fn(...args);
    });

    return this;
  }

  once(event, callback) {
    let wrapFun = function(...args) {
      callback(...args);

      this.off(event, wrapFun);
    };
    this.on(event, wrapFun);

    return this;
  }
}

155.一道常被人輕視的前端 JS 面試題

function Foo() {
  getName = function() {
    alert(1);
  };
  return this;
}
Foo.getName = function() {
  alert(2);
};
Foo.prototype.getName = function() {
  alert(3);
};
var getName = function() {
  alert(4);
};
function getName() {
  alert(5);
}

//請寫出以下輸出結果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市差牛,隨后出現的幾起案子命锄,更是在濱河造成了極大的恐慌,老刑警劉巖偏化,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脐恩,死亡現場離奇詭異,居然都是意外死亡侦讨,警方通過查閱死者的電腦和手機驶冒,發(fā)現死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門析孽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人只怎,你說我怎么就攤上這事袜瞬。” “怎么了身堡?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵邓尤,是天一觀的道長。 經常有香客問我贴谎,道長汞扎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任擅这,我火速辦了婚禮澈魄,結果婚禮上,老公的妹妹穿的比我還像新娘仲翎。我一直安慰自己痹扇,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布溯香。 她就那樣靜靜地躺著鲫构,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玫坛。 梳的紋絲不亂的頭發(fā)上结笨,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音湿镀,去河邊找鬼炕吸。 笑死,一個胖子當著我的面吹牛勉痴,可吹牛的內容都是我干的赫模。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼蚀腿,長吁一口氣:“原來是場噩夢啊……” “哼嘴瓤!你這毒婦竟也來了?” 一聲冷哼從身側響起莉钙,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤廓脆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后磁玉,有當地人在樹林里發(fā)現了一具尸體停忿,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年蚊伞,在試婚紗的時候發(fā)現自己被綠了席赂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吮铭。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颅停,靈堂內的尸體忽然破棺而出谓晌,到底是詐尸還是另有隱情,我是刑警寧澤癞揉,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布纸肉,位于F島的核電站,受9級特大地震影響喊熟,放射性物質發(fā)生泄漏柏肪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一芥牌、第九天 我趴在偏房一處隱蔽的房頂上張望烦味。 院中可真熱鬧,春花似錦壁拉、人聲如沸谬俄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凤瘦。三九已至,卻和暖如春案铺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梆靖。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工控汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人返吻。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓姑子,卻偏偏與公主長得像,于是被迫代替她去往敵國和親测僵。 傳聞我的和親對象是個殘疾皇子街佑,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355