前端 30 問:愿你能三十而立

1.輸出的結(jié)果是什么? 怎么改變循環(huán)1輸出結(jié)果變成循環(huán)2的打印結(jié)果
for(var i = 0;i<5;i++){
  setTimeout(()=>{
    console.log(i) //五次5
  },1000)
  console.log(i) //0 1 2 3 4
}
for(let i = 0;i<5;i++){
  setTimeout(()=>{
    console.log(i) // 0 1 2 3 4
  },1000)
}
// 改變
for(var i = 0;i<5;i++){
  (function(j) {
      setTimeout( function timer() {
          console.log( j );
      }, j*1000 );
  })(i);
}
復(fù)制代碼
這是因?yàn)閟etTimeout是異步執(zhí)行,每一次for循環(huán)的時候,setTimeout都執(zhí)行一次,但是里面的函數(shù)沒有被執(zhí)行,而是被放到了任務(wù)隊(duì)列里,等待執(zhí)行。只有主線上的任務(wù)執(zhí)行完,才會執(zhí)行任務(wù)隊(duì)列里的任務(wù)。也就是說它會等到for循環(huán)全部運(yùn)行完畢后,才會執(zhí)行fun函數(shù),但是當(dāng)for循環(huán)結(jié)束后此時i的值已經(jīng)變成了5,因此雖然定時器跑了5秒,控制臺上的內(nèi)容依然是5。

同時因?yàn)閒or循環(huán)頭部的let不僅將i綁定到for循環(huán)中,事實(shí)上它將其重新綁定到循環(huán)體的每一次迭代中,確保上一次迭代結(jié)束的值重新被賦值。setTimeout里面的function()屬于一個新的域,通過var定義的變量是無法傳入到這個函數(shù)執(zhí)行域中的,通過使用let來聲明塊變量能作用于這個塊,所以function就能使用i這個變量了;這個匿名函數(shù)的參數(shù)作用域和for參數(shù)的作用域不一樣,是利用了這一點(diǎn)來完成的。這個匿名函數(shù)的作用域有點(diǎn)類似類的屬性,是可以被內(nèi)層方法使用的。

最后呢 使用var聲明的變量會存在變量提升的問題,而使用let就不存在變量提升的問題。如果非要使用var當(dāng)做循環(huán)循環(huán)頭的話,出現(xiàn)循環(huán)后打印出的結(jié)果一模一樣的問題,可以使用傳參或者閉包的方式

2.數(shù)組循環(huán)方式有哪些
(1)、forEach:會遍歷數(shù)組, 沒有返回值, 不允許在循環(huán)體內(nèi)寫return, 會改變原來數(shù)組的內(nèi)容.forEach()也可以循環(huán)對象

(2)、map:遍歷數(shù)組, 會返回一個新數(shù)組, 不會改變原來數(shù)組里的內(nèi)容

(3)、filter:會過濾掉數(shù)組中不滿足條件的元素, 把滿足條件的元素放到一個新數(shù)組中, 不改變原數(shù)組

(4)、reduce:

let array = [1, 2, 3, 4];
let temp = array.reduce((x, y) => {
 console.log("x,"+x);
 console.log("y,"+y);
 console.log("x+y,",Number(x)+Number(y));
 return x + y;
});
console.log(temp);  // 10
console.log(array);  // [1, 2, 3, 4]
復(fù)制代碼
(5)、every:遍歷數(shù)組, 每一項(xiàng)都是true, 則返回true, 只要有一個是false, 就返回false

(6)、some:遍歷數(shù)組的每一項(xiàng), 有一個返回true, 就停止循環(huán)

3.script標(biāo)簽中defer和async的區(qū)別?
defer:script被異步加載后并不會?刻執(zhí)?,而是要等到整個頁面在內(nèi)存中正常渲染結(jié)束后,才會執(zhí)行。多 個 defer 腳本會按照它們在頁面出現(xiàn)的順序加載。

async:同樣是異步加載腳本,區(qū)別是腳本加載完畢后?即執(zhí)?,這導(dǎo)致async屬性下的腳本是亂序的,對于script有先后依賴關(guān)系的情況,并不適?。

4.用 setTimeout()方法來模擬 setInterval()與 setInterval()之間的什么區(qū)別?
首先來看 setInterval 的缺陷,使用 setInterval()創(chuàng)建的定時器確保了定時器代碼規(guī)則地插 入隊(duì)列中。這種方式的問題在于,如果定時器代碼在代碼再次添加到隊(duì)列之前還沒完成執(zhí)行, 結(jié)果就會導(dǎo)致定時器代碼連續(xù)運(yùn)行好幾次,而之間沒有間隔。所以可能會出現(xiàn)下面的情況:當(dāng)前執(zhí)行棧執(zhí)行的時間很長,導(dǎo)致事件隊(duì)列里邊積累多個定時器加入的事件,當(dāng)執(zhí)行棧結(jié)束后,這些事件會依次執(zhí)行而之間沒有任何停頓,不能達(dá)到間隔一段時間執(zhí)行的效果。

不過幸運(yùn)的是:javascript引擎足夠聰明,能夠避免這個問題。當(dāng)使用 setInterval()時,僅當(dāng)沒有該定時器的任何其他代碼實(shí)例時,才將定時器代碼添加到隊(duì)列中。這確保了定時器代碼加入隊(duì)列中最小的時間間隔為指定時間。這種重復(fù)定時器的規(guī)則有兩個問題:

1.某些間隔會被跳過 ;

2.多個定時器的代碼執(zhí)行時間可能會比預(yù)期小 ;

eg:假設(shè),某個 onclick 事件處理程序使用啦 setInterval()來設(shè)置了一個 200ms 的重復(fù)定時器。

如果事件處理程序花了 300ms 多一點(diǎn)的時間完成。


注:這個例子中的第 1 個定時器是在 205ms 處添加到隊(duì)列中的,但是直到過了 300ms 處才能夠執(zhí)行。當(dāng)執(zhí)行這個定時器代碼時,在 405ms 處又給隊(duì)列添加了另外一個副本。在下一個間隔,即 605ms 處,第一個定時器代碼仍在運(yùn)行,同時在隊(duì)列中已經(jīng)有了一個定時器代碼的實(shí)例。結(jié)果是,在這個時間點(diǎn)上的定時器代碼不會被添加到隊(duì)列中。結(jié)果在 5ms 處添加的定時器代碼結(jié)束之后,405ms 處添加的定時器代碼就立刻執(zhí)行\(zhòng)

所以如果用setTimeout替代的話,就可以確保只有一個事件結(jié)束了之后,才會觸發(fā)下一個定時器事件。這確保了定時器代碼加入到隊(duì)列中的最小時間間隔為指定間隔。

以下是實(shí)現(xiàn)的2種簡單方式:

function say(){
  //something
  setTimeout(say,200);
}
setTimeout(say,200)

// 或者

setTimeout(function(){
  //do something
  setTimeout(arguments.callee,200);
},200);
復(fù)制代碼
5.JS 怎么控制一次加載一張圖片,加載完后再加載下一張
(1)方法 1

<script type="text/javascript">
  var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onload=function(){
      alert('圖片的寬度為:'+obj.width+';圖片的高度為:'+obj.height);
      document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
    }
</script>

<div id="mypic">onloading……</div>
復(fù)制代碼
(2)方法 2

<script type="text/javascript">
  var obj=new Image();
    obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
    obj.onreadystatechange=function(){
  if(this.readyState=="complete"){
    alert('圖片的寬度為:'+obj.width+';圖片的高度為:'+obj.height);
    document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
    }
  }
</script>

  <div id="mypic">onloading……</div>
復(fù)制代碼
6.如何實(shí)現(xiàn) sleep 的效果(es5 或者 es6)
(1)while 循環(huán)的方式

function sleep(ms){
  var start=Date.now(),expire=start+ms;
  while(Date.now()<expire);
  console.log('1111');
  return;
}
復(fù)制代碼
執(zhí)行 sleep(1000)之后,休眠了 1000ms 之后輸出了 1111。上述循環(huán)的方式缺點(diǎn)很明顯,容易造成死循環(huán)。

(2)通過 promise 來實(shí)現(xiàn)

function sleep(ms){
  var temple=new Promise(
    (resolve)=>{
      console.log(111);
      setTimeout(resolve,ms);
    });
  return temple
}

sleep(500).then(
  function(){
    //console.log(222)
})
復(fù)制代碼
//先輸出了 111,延遲 500ms 后輸出 222

(3)通過 async 封裝

function sleep(ms){
  return new Promise((resolve)=>setTimeout(resolve,ms));
}
async function test(){
  var temple=await sleep(1000);
  console.log(1111)
  return temple
}
test();
復(fù)制代碼
//延遲 1000ms 輸出了 1111






(4).通過 generate 來實(shí)現(xiàn)

function* sleep(ms){
  yield new Promise(function(resolve,reject){
    console.log(111);
    setTimeout(resolve,ms);
  })
}

sleep(500).next().value.then(
  function(){
    console.log(2222)
})
復(fù)制代碼
7.實(shí)現(xiàn) JS 中所有對象的深度克?。òb對象,Date 對象,正則對象)
通過遞歸可以簡單實(shí)現(xiàn)對象的深度克隆,但是這種方法不管是 ES6 還是 ES5 實(shí)現(xiàn),都有 同樣的缺陷,就是只能實(shí)現(xiàn)特定的 object 的深度復(fù)制(比如數(shù)組和函數(shù)),不能實(shí)現(xiàn)包裝對象 Number,String ,Boolean,以及 Date 對象,RegExp 對象的復(fù)制。

(1)前文的方法

function deepClone(obj){
  var newObj= obj instanceof Array?[]:{};
  for(var i in obj){
    newObj[i] = typeof obj[i] == 'object' ? deepClone(obj[i]):obj[i];
  }
  return newObj;
}
復(fù)制代碼
這種方法可以實(shí)現(xiàn)一般對象和數(shù)組對象的克隆,比如:

var arr=[1,2,3];
var newArr=deepClone(arr);
// newArr->[1,2,3]
var obj={
  x:1,
  y:2
}
var newObj=deepClone(obj);
// newObj={x:1,y:2}
復(fù)制代碼
但是不能實(shí)現(xiàn)例如包裝對象 Number,String,Boolean,以及正則對象 RegExp 和 Date 對象的

克隆,比如:

//Number 包裝對象
var num=new Number(1);
typeof num // "object"
var newNum=deepClone(num);
//newNum -> {} 空對象

//String 包裝對象
var str=new String("hello");
typeof str //"object"
var newStr=deepClone(str);
//newStr-> {0:'h',1:'e',2:'l',3:'l',4:'o'};

//Boolean 包裝對象
var bol=new Boolean(true);
typeof bol //"object"
var newBol=deepClone(bol);
// newBol ->{} 空對象  
復(fù)制代碼
(2)valueof()函數(shù)

所有對象都有 valueOf 方法,valueOf 方法對于:如果存在任意原始值,它就默認(rèn)將對象轉(zhuǎn)換為表示它的原始值。對象是復(fù)合值,而且大多數(shù)對象無法真正表示為一個原始值,因此默認(rèn)的 valueOf()方法簡單地返回對象本身,而不是返回一個原始值。數(shù)組、函數(shù)和正則表達(dá)式簡單地繼承了這個默認(rèn)方法,調(diào)用這些類型的實(shí)例的 valueOf()方法只是簡單返回這個對象本身。

對于原始值或者包裝類:

function baseClone(base){
  return base.valueOf();
}

//Number
var num=new Number(1);
var newNum=baseClone(num);
//newNum->1

//String
var str=new String('hello');
var newStr=baseClone(str);
// newStr->"hello"

//Boolean
var bol=new Boolean(true);
var newBol=baseClone(bol);
//newBol-> true
復(fù)制代碼
其實(shí)對于包裝類,完全可以用=號來進(jìn)行克隆,其實(shí)沒有深度克隆一說,這里用 valueOf 實(shí)現(xiàn),語法上比較符合規(guī)范。

對于 Date 類型:

因?yàn)?valueOf 方法,日期類定義的 valueOf()方法會返回它的一個內(nèi)部表示:1970 年 1 月 1 日以來的毫秒數(shù).因此我們可以在 Date 的原型上定義克隆的方法:

Date.prototype.clone=function(){
  return new Date(this.valueOf());
}
var date=new Date('2010');
var newDate=date.clone();
// newDate-> Fri Jan 01 2010 08:00:00 GMT+0800
復(fù)制代碼
對于正則對象 RegExp:

RegExp.prototype.clone = function() {
  var pattern = this.valueOf();
  var flags = '';
  flags += pattern.global ? 'g' : '';
  flags += pattern.ignoreCase ? 'i' : '';
  flags += pattern.multiline ? 'm' : '';
  return new RegExp(pattern.source, flags);
};
var reg=new RegExp('/111/');
var newReg=reg.clone();
//newReg-> //111//
復(fù)制代碼
8.JS 監(jiān)聽對象屬性的改變
我們假設(shè)這里有一個 user 對象,

(1)在 ES5 中可以通過 Object.defineProperty 來實(shí)現(xiàn)已有屬性的監(jiān)聽

Object.defineProperty(user,'name',{
 set:function(key,value){}
})
//缺點(diǎn):如果 id 不在 user 對象中,則不能監(jiān)聽 id 的變化
復(fù)制代碼
(2)在 ES6 中可以通過 Proxy 來實(shí)現(xiàn)






var user = new Proxy({},{
 set:function(target,key,value,receiver){}
})
//這樣即使有屬性在 user 中不存在,通過 user.id 來定義也同樣可以這樣監(jiān)聽這個屬性的變化
復(fù)制代碼
9.Vue3.0 里為什么要用 Proxy API 替代 defineProperty API?
答:響應(yīng)式優(yōu)化。

(1)defineProperty API 的局限性最大原因是它只能針對單例屬性做監(jiān)聽。

Vue2.x 中的響應(yīng)式實(shí)現(xiàn)正是基于 defineProperty 中的 descriptor,對 data 中的屬性做了遍歷 + 遞歸,為每個屬性設(shè)置了 getter、setter。

這也就是為什么 Vue 只能對 data 中預(yù)定義過的屬性做出響應(yīng)的原因,在 Vue 中使用下標(biāo)的方式直接修改屬性的值或者添加一個預(yù)先不存在的對象屬性是無法做到 setter 監(jiān)聽的,這是 defineProperty 的局限性。

(2)Proxy API 的監(jiān)聽是針對一個對象的,這就完全可以代理所有屬性。

Proxy API 監(jiān)聽一個對象,那么對這個對象的所有操作會進(jìn)入監(jiān)聽操作,從而就完全可以代理所有屬性,可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫,這樣就帶來了很大的性能提升和更優(yōu)的代碼。

(3)響應(yīng)式是惰性的

在 Vue.js 2.x 中,對于一個深層屬性嵌套的對象,要劫持它內(nèi)部深層次的變化,就需要遞歸遍歷這個對象,執(zhí)行 Object.defineProperty 把每一層對象數(shù)據(jù)都變成響應(yīng)式的,這無疑會有很大的性能消耗。

在 Vue.js 3.0 中,使用 Proxy API 并不能監(jiān)聽到對象內(nèi)部深層次的屬性變化,因此它的處理方式是在 getter 中去遞歸響應(yīng)式,這樣的好處是真正訪問到的內(nèi)部屬性才會變成響應(yīng)式,簡單的可以說是按需實(shí)現(xiàn)響應(yīng)式,減少性能消耗。

10、js 中宏觀任務(wù)、微觀任務(wù)、循環(huán)機(jī)制的理解
js 屬于單線程,分為同步和異步,js代碼是自上向下執(zhí)行的,在主線程中立即執(zhí)行的就是同步任務(wù),比如簡單的邏輯操作及函數(shù),而異步任務(wù)不會立馬執(zhí)行,會挪步放到到異步隊(duì)列中,比如ajax、promise、事件、計(jì)時器等等。

先執(zhí)行同步,主線程結(jié)束后再按照異步的順序再次執(zhí)行。

事件循環(huán):同步任務(wù)進(jìn)入主線程,立即執(zhí)行,執(zhí)行之后異步任務(wù)進(jìn)入主線程,這樣循環(huán)。

在事件循環(huán)中,每進(jìn)行一次循環(huán)操作稱為tick,tick 的任務(wù)處理模型是比較復(fù)雜的,里邊有兩個詞:分別是 Macro Task (宏任務(wù))和 Micro Task(微任務(wù))

宏觀任務(wù)和微觀任務(wù)(先執(zhí)行微觀任務(wù),再執(zhí)行宏觀任務(wù))

針對js

宏觀任務(wù):setTimeOut、setTimeInterval、postMessage

微觀任務(wù):Promise.then

重點(diǎn):Promise 同步 Promise.then 異步

console.log("開始111");

setTimeout(function() {

  console.log("setTimeout111");

}, 0);

Promise.resolve().then(function() {

  console.log("promise111");

}).then(function() {

  console.log("promise222");

});

console.log("開始222");
復(fù)制代碼
我們按照步驟來分析下:

1、遇到同步任務(wù),直接先打印 “開始111”。

2、遇到異步 setTimeout ,先放到隊(duì)列中等待執(zhí)行。

3、遇到了 Promise ,放到等待隊(duì)列中。

4、遇到同步任務(wù),直接打印 “開始222”。

5、同步執(zhí)行完,返回執(zhí)行隊(duì)列中的代碼,從上往下執(zhí)行,發(fā)現(xiàn)有宏觀任務(wù) setTimeout 和微觀任務(wù) Promise ,那么先執(zhí)行微觀任務(wù),再執(zhí)行宏觀任務(wù)。

所以打印的順序?yàn)椋洪_始111 、開始222 、 promise111 、 promise222 、 setTimeout111 。






11. rgb和16進(jìn)制顏色轉(zhuǎn)換
首先我們需要知道RGB與十六進(jìn)制之間的關(guān)系,例如我們最常見的白色RGB表示為rgb(255, 255, 255), 十六進(jìn)制表示為#FFFFFFF, 我們可以把十六進(jìn)制顏色除‘#’外按兩位分割成一部分,即FF,FF,FF, 看一下十六進(jìn)制的FF轉(zhuǎn)為十進(jìn)制是多少呢?沒錯,就是255!

了解了十六進(jìn)制和RGB關(guān)系之后,我們就會發(fā)現(xiàn)RGB轉(zhuǎn)十六進(jìn)制方法就很簡單了

將RGB的3個數(shù)值分別轉(zhuǎn)為十六進(jìn)制數(shù),然后拼接,即 rgb(255, 255, 255) => ‘#’ + ‘FF’ + ‘FF’ + ‘FF’。巧妙利用左移,我們把十六進(jìn)制數(shù)值部分當(dāng)成一個整數(shù),即FFFFFF,我們可以理解為FF0000 + FF00 + FF, 如同我們上面解釋,如果左移是基于十六進(jìn)制計(jì)算的,則可以理解為FF << 4, FF << 2, FF, 而實(shí)際上我們轉(zhuǎn)為二進(jìn)制則變?yōu)?FF << 16,如下:

x * 16^4 = x * 2 ^ 16

了解了原理以后,代碼如下:

function RGBFUN(rgb){
    // 取出rgb中的數(shù)值
    let arr = rgb.match(/\d+/g);
    console.log(arr,'輸出rgb轉(zhuǎn)換數(shù)值') //[ '0', '255', '0' ] 輸出rgb轉(zhuǎn)換數(shù)值
    if (!arr || arr.length !== 3) {
        console.error('rgb數(shù)值不合法');
        return
    }
    let hex = (arr[0]<<16 | arr[1]<<8 | arr[2]).toString(16);
    // 自動補(bǔ)全第一位
    if (hex.length < 6) {
        hex = '0' + hex;
    }
    console.log(`#${hex}`,'輸出轉(zhuǎn)換之后的數(shù)值') // #0ff00 輸出轉(zhuǎn)換之后的數(shù)值
    return `#${hex}`;
}
RGBFUN('rgb(0,255,0)')
復(fù)制代碼
12. JavaScript位操作:兩個操作數(shù)相應(yīng)的比特位有且只有一個1時,結(jié)果為1,否則為0
可以實(shí)現(xiàn)簡單的加密

const value = 456;
function encryption(str) {
    let s = '';
    str.split('').map(item => {
    s += handle(item);
    })
    return s;
}

function decryption(str) {
let s = '';
str.split('').map(item => {
    s += handle(item);
})
return s;
}

function handle(str) {
    if (/\d/.test(str)) {
    return str ^ value;
    } else {
    let code = str.charCodeAt();
    let newCode = code ^ value;
    return String.fromCharCode(newCode);
    }
}

let init = 'JS 掌握 位運(yùn)算';
let result = encryption(init);             // ???扄戩?亅踘穟 加密之后
console.log(result,'加密之后')
let decodeResult = decryption(result);     // JS 掌握 位運(yùn)算 解密之后
console.log(decodeResult,'解密之后')
復(fù)制代碼
13.【基于webpack】有哪些Loader?用過哪些?
文件

val-loader:將代碼作為模塊執(zhí)行,并將其導(dǎo)出為 JS 代碼
ref-loader:用于手動建立文件之間的依賴關(guān)系
JSON

cson-loader:加載并轉(zhuǎn)換 CSON 文件
語法轉(zhuǎn)換

babel-loader:使用 Babel 加載 ES2015+ 代碼并將其轉(zhuǎn)換為 ES5
esbuild-loader:加載 ES2015+ 代碼并使用 esbuild 轉(zhuǎn)譯到 ES6+
buble-loader:使用 Bublé 加載 ES2015+ 代碼并將其轉(zhuǎn)換為 ES5
traceur-loader:使用 Traceur 加載 ES2015+ 代碼并將其轉(zhuǎn)換為 ES5
ts-loader:像加載 JavaScript 一樣加載 TypeScript 2.0+
coffee-loader:像加載 JavaScript 一樣加載 CoffeeScript
fengari-loader:使用 fengari 加載 Lua 代碼
elm-webpack-loader:像加載 JavaScript 一樣加載 Elm
模板

html-loader:將 HTML 導(dǎo)出為字符串,需要傳入靜態(tài)資源的引用路徑
pug-loader:加載 Pug 和 Jade 模板并返回一個函數(shù)
markdown-loader:將 Markdown 編譯為 HTML
react-markdown-loader:使用 markdown-parse 解析器將 Markdown 編譯為 React 組件
posthtml-loader:使用 PostHTML 加載并轉(zhuǎn)換 HTML 文件
handlebars-loader:將 Handlebars 文件編譯為 HTML
markup-inline-loader:將 SVG/MathML 文件內(nèi)嵌到 HTML 中。在將圖標(biāo)字體或 CSS 動畫應(yīng)用于 SVG 時,此功能非常實(shí)用。
twig-loader:編譯 Twig 模板并返回一個函數(shù)
remark-loader:通過 remark 加載 markdown,且支持解析內(nèi)容中的圖片
樣式

style-loader:將模塊導(dǎo)出的內(nèi)容作為樣式并添加到 DOM 中
css-loader:加載 CSS 文件并解析 import 的 CSS 文件,最終返回 CSS 代碼
less-loader:加載并編譯 LESS 文件
sass-loader:加載并編譯 SASS/SCSS 文件
postcss-loader:使用 PostCSS 加載并轉(zhuǎn)換 CSS/SSS 文件
stylus-loader:加載并編譯 Stylus 文件
框架

vue-loader:加載并編譯 Vue 組件
angular2-template-loader:加載并編譯 Angular 組件
14.談?wù)凧s如何實(shí)現(xiàn)繼承?
1.原型鏈繼承:將父類的實(shí)例作為子類的原型

優(yōu)點(diǎn):

簡單,易于實(shí)現(xiàn)
父類新增原型方法、原型屬性,子類都能訪問到
缺點(diǎn):

無法實(shí)現(xiàn)多繼承,因?yàn)樵鸵淮沃荒鼙灰粋€實(shí)例更改
來自原型對象的所有屬性被所有實(shí)例共享(上訴例子中的color屬性)
創(chuàng)建子類實(shí)例時,無法向父構(gòu)造函數(shù)傳參
2.構(gòu)造函數(shù)繼承:復(fù)制父類的實(shí)例屬性給子類

優(yōu)點(diǎn):

解決了原型鏈繼承中子類實(shí)例共享父類引用屬性的問題
創(chuàng)建子類實(shí)例時,可以向父類傳遞參數(shù)
可以實(shí)現(xiàn)多繼承(call多個父類對象)
缺點(diǎn):

實(shí)例并不是父類的實(shí)例,只是子類的實(shí)例
只能繼承父類實(shí)例的屬性和方法,不能繼承其原型上的屬性和方法
無法實(shí)現(xiàn)函數(shù)復(fù)用,每個子類都有父類實(shí)例函數(shù)的副本,影響性能
3.組合繼承(經(jīng)典繼承):將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊。使用原型鏈實(shí)現(xiàn)對原型屬性和方法的繼承,而通過構(gòu)造函數(shù)來實(shí)現(xiàn)對實(shí)例屬性的繼承

優(yōu)點(diǎn):

彌補(bǔ)了構(gòu)造繼承的缺點(diǎn),現(xiàn)在既可以繼承實(shí)例的屬性和方法,也可以繼承原型的屬性和方法
既是子類的實(shí)例,也是父類的實(shí)例
可以向父類傳遞參數(shù)
函數(shù)可以復(fù)用
缺點(diǎn):

調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實(shí)例
constructor指向問題
4.實(shí)例繼承:為父類實(shí)例添加新特征,作為子類實(shí)例返回

優(yōu)點(diǎn):

不限制調(diào)用方式,不管是new 子類()還是子類(),返回的對象具有相同的效果
缺點(diǎn):

實(shí)例是父類的實(shí)例,不是子類的實(shí)例
不支持多繼承
5.拷貝繼承:對父類實(shí)例中的的方法與屬性拷貝給子類的原型

優(yōu)點(diǎn):

支持多繼承
缺點(diǎn):

效率低,性能差,占用內(nèi)存高(因?yàn)樾枰截惛割悓傩裕?br>無法獲取父類不可枚舉的方法(不可枚舉的方法,不能使用for-in訪問到)
6.寄生組合繼承:通過寄生方式,砍掉父類的實(shí)例屬性,避免了組合繼承生成兩份實(shí)例的缺點(diǎn)

優(yōu)點(diǎn):

比較完美(js實(shí)現(xiàn)繼承首選方式)
缺點(diǎn):

1.實(shí)現(xiàn)起來較為復(fù)雜(可通過Object.create簡化)

7.es6--Class繼承:使用extends表明繼承自哪個父類,并且在子類構(gòu)造函數(shù)中必須調(diào)用super

優(yōu)點(diǎn):

1.語法簡單易懂,操作更方便

缺點(diǎn):

1.并不是所有的瀏覽器都支持class關(guān)鍵字 lass Per

15.最終結(jié)果會輸出什么?
var length = 10;
function fn(){
    console.log(this.length)
}
var obj = {
    length: 5,
    method: function(fn){
        fn();
        arguments[0]()
    }
}
obj.method(fn,1)   //10  2
復(fù)制代碼
解析:

1、fn() :任意函數(shù)里如果嵌套了非箭頭函數(shù),那這個時候,嵌套函數(shù)里的 this 在未指定的情況下,應(yīng)該指向的是 window 對象,所以這里執(zhí)行 fn 會打印 window.length。如果使用 let 聲明變量時會形成塊級作用于,且不存在變量提升;而 var 存在聲明提升。所以是輸出結(jié)果是10;但是如果是let length = 10;那輸出結(jié)果就是:0 2。

2、arguments0 :在方法調(diào)用(如果某個對象的屬性是函數(shù),這個屬性就叫方法,調(diào)用這個屬性,就叫方法調(diào)用)中,執(zhí)行函數(shù)體的時候,作為屬性訪問主體的對象和數(shù)組便是其調(diào)用方法內(nèi) this 的指向。(通俗的說,調(diào)用誰的方法 this 就指向誰)。

這里 arguments0 是作為 arguments 對象的屬性 [0] 來調(diào)用 fn 的,所以 fn 中的 this 指向?qū)傩栽L問主體的對象 arguments。

這里還有一點(diǎn)要注意,arguments 這個參數(shù),指的是 function 的所有實(shí)參,與形參無關(guān)。也就是調(diào)用函數(shù)時,實(shí)際傳入的參數(shù),fn 與 1 作為一個參數(shù)存儲在 arguments 對象里,所以 arguments 的長度為2,所以輸出 2。

16. 如何實(shí)現(xiàn)CLI創(chuàng)建模板項(xiàng)目?
講述實(shí)現(xiàn)的基本步驟:
終端參數(shù)解析
CLI交互收集創(chuàng)建項(xiàng)目必要參數(shù)
拷貝模板項(xiàng)目
重寫package.json文件(修改項(xiàng)目名稱等)
創(chuàng)建完成后提示或執(zhí)行項(xiàng)目的初始化過程
講述實(shí)現(xiàn)過程的注意事項(xiàng):
package.json中name的命名規(guī)范
同名項(xiàng)目再次被創(chuàng)建需要考慮是否覆蓋或中斷,覆蓋的情況下要考慮已創(chuàng)建的項(xiàng)目是否接入的版本管理
創(chuàng)建完成后提示或執(zhí)行項(xiàng)目初始化要根據(jù)使用時的包管理器來判斷
內(nèi)容擴(kuò)展:
在CLI交互收集參數(shù)可以支持歷史項(xiàng)目模板或第三方模板,通過對應(yīng)模板的創(chuàng)建方式來實(shí)現(xiàn)兼容
CLI創(chuàng)建項(xiàng)目通常要先執(zhí)行安裝CLI的命令,再來運(yùn)行CLI內(nèi)置的創(chuàng)建項(xiàng)目命令兩步,可以考慮使用命名規(guī)約來實(shí)現(xiàn)直接執(zhí)行npm create xxx、yarn create xxx 啟動CLI創(chuàng)建項(xiàng)目
17. 如何實(shí)現(xiàn)自動化埋點(diǎn)?
自動化埋點(diǎn)可以利用 Babel + 定制埋點(diǎn)插件在項(xiàng)目編譯過程中對函數(shù)插樁實(shí)現(xiàn)埋點(diǎn)自動化,使用Babel自動化插樁需要關(guān)注兩點(diǎn):

埋點(diǎn)模塊判斷是否已經(jīng)導(dǎo)入和如何導(dǎo)入:
在 Babel 插件執(zhí)行中通過遍歷代碼中的ImportDeclaration來分析是否包含了指定的埋點(diǎn)模塊;
通過Babel內(nèi)置@babel/helper-module-imports模塊來實(shí)現(xiàn)埋點(diǎn)模塊導(dǎo)入;
埋點(diǎn)函數(shù)如何生成并插入到源函數(shù);
如何生成:通過內(nèi)置APItemplate.statement來生成調(diào)用埋點(diǎn)函數(shù)的AST;
如何插入:函數(shù)的不同形式對應(yīng)的AST中類型包括了ClassMethod、ArrowFunctionExpression、FunctionExpression、FunctionDeclaration,在 Babel 遍歷它們的時候在對應(yīng)的節(jié)點(diǎn)的body中插入生成的AST,當(dāng)遇到?jīng)]有函數(shù)體的函數(shù)需要增加函數(shù)體并處理返回值;
18. 講述一下JSON Web Token?
工作原理:
JWT在服務(wù)器認(rèn)證后生成包含用戶信息,時間時間,安全信息等內(nèi)容組成的JSON對象來唯一表示當(dāng)前用戶狀態(tài),在其后的數(shù)據(jù)交互中持續(xù)攜帶來表明請求的有效性。

數(shù)據(jù)結(jié)構(gòu):

JWT是有header,payload和signature三部分通過“.”連接起來的字符串,在JWT字符串中沒有換行。

header是一個JSON對象,用來描述一些元數(shù)據(jù);
payload的格式要求同header,內(nèi)容主要官方定義字段+自定義的字段來滿足業(yè)務(wù)場景的需要;
“Header”和“Payload”都沒有提到加密一說,只是進(jìn)行的字符的編碼,所以在“Header”和“Payload”中我們不應(yīng)該放置一些用戶相關(guān)的涉及安全的信息,未防止上述兩塊的內(nèi)容被中間商攔截篡改,所以需要用到“Signature”;
signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)
復(fù)制代碼
使用方式:
標(biāo)準(zhǔn)的使用方式為在HTTP的頭部增加key為“Authorization”,value為:“ Bearer ”的一組信息,token的具體存儲按實(shí)際業(yè)務(wù)處理。

注意事項(xiàng):
JWT默認(rèn)不加密,但可以自行加密處理。
采用默認(rèn)不加密的情況,請勿將涉密數(shù)據(jù)放入JWT中。
建議采用HTTPS來防止中間人攻擊。
19. JavaScript中toString()和join()的區(qū)別?
// 聲明兩個不同的數(shù)組
const fruitList = ['蘋果', '香蕉', '百香果']
const objList = [
  {
    name: '張三',
    age: 16
  },
  {
    name: '李斯',
    age: 18    
  }
]
// 通過toString()直接進(jìn)行轉(zhuǎn)換為字符串
console.log(fruitList.toString()) // '蘋果,香蕉,百香果'
console.log(objList.toString()) // '[object Object],[object Object]'

// 通過join()自定義拼接進(jìn)行轉(zhuǎn)換為字符串
console.log(fruitList.join()) // '蘋果,香蕉,百香果'
console.log(fruitList.join(',')) // '蘋果,香蕉,百香果'
console.log(fruitList.join('、')) // '蘋果、香蕉、百香果'
console.log(fruitList.join('分割')) // '蘋果分割香蕉分割百香果'
console.log(objList.join()) // '[object Object],[object Object]'
復(fù)制代碼
總結(jié)結(jié)論:

toString()與join()是將數(shù)組中的所有元素放入一個字符串。

當(dāng)join()函數(shù)內(nèi)部參數(shù)為空時,join()等價(jià)于toString()。

當(dāng)join(‘,’)函數(shù)內(nèi)部參數(shù)為逗號字符,則表示用逗號分割數(shù)組,等價(jià)于內(nèi)部參數(shù)為空。

當(dāng)join(‘、’)函數(shù)內(nèi)部參數(shù)為頓號字符,則表示用頓號分割數(shù)組。

當(dāng)join(‘分割’)函數(shù)內(nèi)部參數(shù)為字符串時,則表示用‘分割’字符串分割數(shù)組。

20. null / undefined 的區(qū)別
null值:屬于null類型,代表“空值",代表一個空對象指針;使用typeof運(yùn)算得到 “object",所以你可以認(rèn)為它是一個特殊的對象值。

undefined值:屬于undefined類型,當(dāng)一個聲明的變量未初始化賦值時,得到的就是undefined。使用typeof運(yùn)算得到“undefined"。

21. 循環(huán)遍歷map()與forEach()的區(qū)別?
array.map(function(currentValue, index, arr), thisValue)
復(fù)制代碼
const nums = [4, 9, 16, 25]
const emptyList = []
const doubleNums = nums.map(Math.sqrt)

console.log(doubleNums) // [2, 3, 4, 5]
console.log(nums) // [4, 9, 16, 25]
console.log(emptyList.map(Math.sqrt)) // []
復(fù)制代碼
定義和用法:

map()方法返回一個新數(shù)組,數(shù)組中的元素為原始數(shù)組元素調(diào)用函數(shù)處理后的值。

map()方法按照原始數(shù)組元素順序依次處理元素。

map()不會對空數(shù)組進(jìn)行檢測。

map()不會改變原始數(shù)組。

array.forEach(function(currentValue, index, arr), thisValue)
復(fù)制代碼
const nums = [4, 9, 16, 25]
const emptyList = []
const doubleNums = nums.forEach(Math.sqrt)

console.log(doubleNums) // undefined
console.log(emptyList.forEach(Math.sqrt)) // undefined
復(fù)制代碼
定義和用法:

forEach()方法用于調(diào)用數(shù)組的每個元素,并將元素傳遞給回調(diào)函數(shù)。

forEach()對于空數(shù)組是不會執(zhí)行回調(diào)函數(shù)的。

區(qū)別:

forEach()方法不會返回執(zhí)行結(jié)果,而是undefined。也就是說,forEach()會修改原來的數(shù)組。而map()方法會得到一個新的數(shù)組并返回。

map()會分配內(nèi)存空間存儲新數(shù)組并返回,forEach()不會返回?cái)?shù)據(jù)。

forEach()允許callback更改原始數(shù)組的元素。map()返回新的數(shù)組。

22. for/in 與 for/of有什么區(qū)別?
Array.prototype.method = function () {}
let myArray = [1, 2, 3]
myArray.name = '數(shù)組';

for (let index in myArray) {
  console.log(index) // 0,1,2, name, method
  console.log(myArray[index]) //1,2,3,數(shù)組,f() {}
}

for (let val of myArray) {
  console.log(val) // 1,2,3
}
復(fù)制代碼
最直接的區(qū)別就是:for/in遍歷的是數(shù)組的索引,而for/of遍歷的是數(shù)組元素值。

for/in遍歷的缺陷:

索引是字符串類型的數(shù)字,所以不能直接進(jìn)行幾何運(yùn)算
遍歷順序可能不是實(shí)際的內(nèi)部順序
for/in會遍歷數(shù)組所有的可枚舉屬性,包括原型。
所以一般用來遍歷對象,而不是數(shù)組。

for/of遍歷的缺陷:

for/of不支持普通對象,想遍歷對象的屬性,可以用for/in循環(huán),或者內(nèi)建的Object.keys()方法:

Object.keys(obj)獲取對象的實(shí)例屬性組成的數(shù)組,不包括原型方法和屬性

for(let key of Object.keys(obj)) {
  console.log(key + ":" + Object[key])
}
復(fù)制代碼
for/of語句創(chuàng)建一個循環(huán)來迭代可迭代的對象,允許遍歷Arrays,Strings,Map, Set等可迭代的數(shù)據(jù)結(jié)構(gòu)

// variable 每個迭代的屬性值被分配給該變量。
// iterable 一個具有可枚舉屬性并且可以迭代的對象。
for(let variable of iterable) {
  statement
}
復(fù)制代碼
總體來說:for/in遍歷對象;for/of遍歷數(shù)組

23.css 的渲染層合成是什么,瀏覽器如何創(chuàng)建新的渲染層
在 DOM 樹中每個節(jié)點(diǎn)都會對應(yīng)一個渲染對象(RenderObject),當(dāng)它們的渲染對象處于相同的坐標(biāo)空間(z 軸空間)時,就會形成一個 RenderLayers,也就是渲染層。渲染層將保證頁面元素以正確的順序堆疊,這時候就會出現(xiàn)層合成(composite),從而正確處理透明元素和重疊元素的顯示。對于有位置重疊的元素的頁面,這個過程尤其重要,因?yàn)橐坏﹫D層的合并順序出錯,將會導(dǎo)致元素顯示異常。

瀏覽器如何創(chuàng)建新的渲染層

根元素 document
有明確的定位屬性(relative、fixed、sticky、absolute)
opacity < 1
有 CSS fliter 屬性
有 CSS mask 屬性
有 CSS mix-blend-mode 屬性且值不為 normal
有 CSS transform 屬性且值不為 none
backface-visibility 屬性為 hidden
有 CSS reflection 屬性
有 CSS column-count 屬性且值不為 auto 或者有 CSS column-width 屬性且值不為 auto
當(dāng)前有對于 opacity、transform、fliter、backdrop-filter 應(yīng)用動畫
overflow 不為 visible
24.路由原理 history 和 hash 兩種路由方式的特點(diǎn)
hash 模式

location.hash 的值實(shí)際就是 URL 中#后面的東西 它的特點(diǎn)在于:hash 雖然出現(xiàn) URL 中,但不會被包含在 HTTP 請求中,對后端完全沒有影響,因此改變 hash 不會重新加載頁面。
可以為 hash 的改變添加監(jiān)聽事件
window.addEventListener("hashchange", funcRef, false);

每一次改變 hash(window.location.hash),都會在瀏覽器的訪問歷史中增加一個記錄利用 hash 的以上特點(diǎn),就可以來實(shí)現(xiàn)前端路由“更新視圖但不重新請求頁面”的功能了

特點(diǎn):兼容性好但是不美觀

history 模式

利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。

這兩個方法應(yīng)用于瀏覽器的歷史記錄站,在當(dāng)前已有的 back、forward、go 的基礎(chǔ)之上,它們提供了對歷史記錄進(jìn)行修改的功能。這兩個方法有個共同的特點(diǎn):當(dāng)調(diào)用他們修改瀏覽器歷史記錄棧后,雖然當(dāng)前 URL 改變了,但瀏覽器不會刷新頁面,這就為單頁應(yīng)用前端路由“更新視圖但不重新請求頁面”提供了基礎(chǔ)。

特點(diǎn):雖然美觀,但是刷新會出現(xiàn) 404 需要后端進(jìn)行配置

25.XMLHttpRequest與fetch的區(qū)別?
(1).XMLHttpRequest 對象在所有現(xiàn)代的瀏覽器都支持,F(xiàn)etch 是一種新的原生 JavaScript API,目前大多數(shù)瀏覽器都支持

(2).XMLHttpRequest基于onreadystatechange執(zhí)行回調(diào),F(xiàn)etch API 使用 Promise,避免了回調(diào)。

(3).Fetch只對網(wǎng)絡(luò)請求報(bào)錯,對400,500都當(dāng)做成功的請求,需要封裝去處理。

(4).Fetch 請求默認(rèn)是不帶 cookie 的,需要設(shè)置 fetch(url, {credentials: 'include'})。

(5).Fetch不支持abort,因?yàn)镕etch返回的是一個promise,不支持超時控制,使用setTimeout及Promise.reject的實(shí)現(xiàn)的超時控制并不能阻止請求過程繼續(xù)在后臺運(yùn)行,造成了量的浪費(fèi)。

(6).fetch() 示例中,可以傳遞字符串定義 的URL 端點(diǎn),也可以傳遞一個可配置的 Request 對象。

(7).在 XMLHttpRequest 中管理緩存很麻煩,F(xiàn)etch 對象中內(nèi)置了對緩存的支持:

(8).跨域請求中,F(xiàn)etch 提供了一個模式屬性,可以在參數(shù)中設(shè)置‘no-cors’屬性。

(9).默認(rèn)情況下,fetch() 和 XMLHttpRequest 都遵循服務(wù)器重定向。但是,fetch() 在參數(shù)中提供了可選項(xiàng):

redirect

'follow' —— 遵循所有重定向(默認(rèn))

'error' —— 發(fā)生重定向時中止(拒絕)

'manual' —— 返回手動處理的響應(yīng)

(10).XMLHttpRequest 將整個響應(yīng)讀入內(nèi)存緩沖區(qū),但是 fetch() 可以流式傳輸請求和響應(yīng)數(shù)據(jù),這是一項(xiàng)新技術(shù),流允許你在發(fā)送或接收時處理更小的數(shù)據(jù)塊。

(11).Deno 和 Node 中完全支持 Fetch,XMLHttpRequest 只在客戶端被支持。

26.前端如何實(shí)現(xiàn)大文件下載?
(1).后端服務(wù)支持的情況下可以使用分塊下載。

(2).云上文件可以采用a標(biāo)簽方式,通過瀏覽器默認(rèn)機(jī)制實(shí)現(xiàn)下載

(3).借助StreamAPI與Service Worker實(shí)現(xiàn)

27.常見幾種前端微服務(wù)及實(shí)現(xiàn)原理
微前端架構(gòu)將后端微服務(wù)的理念應(yīng)用于瀏覽器端,即將 Web 應(yīng)用由單一的單體應(yīng)用轉(zhuǎn)變?yōu)槎鄠€小型前端應(yīng)用聚合為一的應(yīng)用。

微前端架構(gòu)一般可以由以下幾種方式進(jìn)行:

路由分發(fā)
通過路由將不同的業(yè)務(wù)分發(fā)到不同的、獨(dú)立前端應(yīng)用上。其通??梢酝ㄟ^ HTTP 服務(wù)器的反向代理來實(shí)現(xiàn),又或者是應(yīng)用框架自帶的路由來解決。

通過iframe容器
使用iframe創(chuàng)建一個全新的獨(dú)立的宿主環(huán)境,運(yùn)行各自獨(dú)立的前端應(yīng)用

前端微服務(wù)化
在頁面合適的地方引入或者創(chuàng)建 DOM,用戶訪問到對應(yīng)URL時,加載對應(yīng)的應(yīng)用,并能卸載應(yīng)用

應(yīng)用微件化
也叫組合式集成,即通過軟件工程的方式在構(gòu)建前、構(gòu)建時、構(gòu)建后等步驟中,對應(yīng)用進(jìn)行一步的拆分,并重新組合

基于Web Components
使用 Web Components 技術(shù)構(gòu)建獨(dú)立于框架的組件,隨后在對應(yīng)的框架中引入這些組件

28.前端部署都有哪些方案?
前端部署的方式大致有三種,

使用Nginx,Tomcat,IIS等web服務(wù)器部署
Dokcer部署
OSS+CDN 部署,或COS
29.常見發(fā)布方案有幾種
藍(lán)綠發(fā)布
藍(lán)綠發(fā)布是指發(fā)布過程中新應(yīng)用發(fā)布測試通過后,通過切換網(wǎng)關(guān)流量, 一鍵升級應(yīng)用的發(fā)布方式

滾動發(fā)布
一般是停止一個或多個服務(wù),執(zhí)行更新,并重新將其投入使用,周而復(fù)始,直到微服務(wù)中所有的因公都更新成新版本。

灰度發(fā)布
是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。AB test就是一種灰度發(fā)布方式,讓一部分用戶繼續(xù)用A,一部分用戶開始用B,如果用戶對B沒有什么反對意見,那么逐步擴(kuò)大范圍,把所有用戶都遷移到B上面來。灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度,而我們平常所說的金絲雀部署也就是灰度發(fā)布的一種方式。

30.如何設(shè)計(jì)一套A/B方案
A/B發(fā)布是一種比較高級的流量分配方案,例如根據(jù) URL 是否匹配請求不同的版本應(yīng)用,或者根據(jù) Header 下某個自定義參數(shù)進(jìn)行用戶的區(qū)分,從而請求不同的版本應(yīng)用

涉及流量管理策略,Istio ,docker,k8s等技術(shù)

當(dāng)前微服務(wù)、云原生的理念逐漸深入,現(xiàn)有前端的開發(fā)方式和前端架構(gòu)也將跟隨著不斷變化,利用好以上技術(shù)能夠?qū)崿F(xiàn)更迅速地?cái)U(kuò)展,更穩(wěn)定地交付,應(yīng)用之間的聯(lián)系也會愈加緊密,未來幾年的前端也必將是微服務(wù)化的時代。

結(jié)語:
看到這里不知道你有沒有得到收獲呢?如果你還有更多的抗凍指南歡迎你在評論區(qū)留言,分享給更多的JY,如果上面的內(nèi)容你還要問題也可以留言交流,愿每一次的互聯(lián)網(wǎng)寒冬都打不倒任何一個有準(zhǔn)備的JY~

作者:高燈科技交易合規(guī)前端團(tuán)隊(duì)GFE

原文:https://juejin.cn/post/7147961072572432421




作者:高燈科技交易合規(guī)前端團(tuán)隊(duì)GFE


歡迎關(guān)注微信公眾號 :深圳灣碼農(nóng)