70道JavaScript面試題

1. undefined 和 null 有什么區(qū)別燎潮?

相似之處

它們屬于 JavaScript 的 7 種基本類型澜公。

使用Boolean(value)或!!value將其轉(zhuǎn)換為布爾值時(shí),值為false

    console.log(!!undefined);   // false
    console.log(!!null);    // false
    
    console.log(Boolean(null)); // false
    console.log(Boolean(undefined)); // false

區(qū)別

undefined是指未指定值的變量的默認(rèn)值聪富,或者函數(shù)沒有指定返回值,會默認(rèn)返回undefined

    let _undefined;
    let fun = function () {}
    let obj = {
        name:'Aiva',
        age:1
    }
    
    console.log(_undefined);    // undefined
    console.log(fun()); // undefined
    console.log(obj.sex);   // undefined

null是“不代表任何值的值”草娜。 null是已明確定義給變量的值塘淑。

在比較null和undefined時(shí)盐须,我們使用==時(shí)得到true,使用===時(shí)得到false:

    console.log(undefined == null);     // true
    console.log(undefined === null);        // false

使用typeof判斷數(shù)據(jù)類型時(shí)衔瓮,undefined會返回undefined,null會返回object浊猾。

    console.log(undefined);     // undefined
    console.log(null);      // object

2. && 運(yùn)算符能做什么?

&& 也叫邏輯與,在表達(dá)式中從左往右查找热鞍,找到一個(gè)虛值表達(dá)式并返回它葫慎,如果沒有找到任何虛值表達(dá)式衔彻,則會返回最后一個(gè)虛值表達(dá)式。

    console.log(1 && 0 && true);    // 0
    console.log(true && 222 && 1);  // 1

在if語句中使用

    let flag;
    if (true && 22) {
        flag = true;
    }else {
        flag = true;
    }
    
    console.log(flag);      // true

3. || 運(yùn)算符能做什么

||也叫或邏輯或偷办,在其操作數(shù)中找到第一個(gè)真值表達(dá)式并返回它艰额。

在支持 ES6 默認(rèn)函數(shù)參數(shù)之前,它用于初始化函數(shù)中的默認(rèn)參數(shù)值椒涯。

    console.log(1 || 0 || true);    // 1
    console.log('' || 0 || 12); // 12
    
    function fun(x) {
        var x = x || 0;
    }

4. 使用 + 或一元加運(yùn)算符是將字符串轉(zhuǎn)換為數(shù)字的最快方法嗎柄沮?

+是將字符串轉(zhuǎn)換為數(shù)字的最快方法,因?yàn)槿绻狄呀?jīng)是數(shù)字废岂,它不會執(zhí)行任何操作祖搓。

5. DOM 是什么?

DOM 代表文檔對象模型湖苞,是 HTML 和 XML 文檔的接口(API)拯欧。當(dāng)瀏覽器第一次讀取(解析)HTML文檔時(shí),它會創(chuàng)建一個(gè)大對象财骨,一個(gè)基于 HTM L文檔的非常大的對象镐作,這就是DOM。它是一個(gè)從 HTML 文檔中建模的樹狀結(jié)構(gòu)蚓再。DOM 用于交互和修改DOM結(jié)構(gòu)或特定元素或節(jié)點(diǎn)滑肉。

JS 中的document對象表示DOM。它為我們提供了許多方法摘仅,我們可以使用這些方法來選擇元素來更新元素內(nèi)容靶庙,等等。

6. 什么是事件傳播?

當(dāng)事件發(fā)生在DOM元素上時(shí)娃属,該事件并不完全發(fā)生在那個(gè)元素上六荒。在“冒泡階段”中,事件冒泡或向上傳播至父級矾端,祖父母掏击,祖父母或父級,直到到達(dá)window為止秩铆;而在“捕獲階段”中砚亭,事件從window開始向下觸發(fā)元素 事件或event.target。

事件傳播有三個(gè)階段:

  1. 捕獲階段–事件從 window 開始殴玛,然后向下到每個(gè)元素捅膘,直到到達(dá)目標(biāo)元素。
  2. 目標(biāo)階段–事件已達(dá)到目標(biāo)元素滚粟。
  3. 冒泡階段–事件從目標(biāo)元素冒泡寻仗,然后上升到每個(gè)元素,直到到達(dá) window凡壤。

7. 什么是事件冒泡署尤?

當(dāng)事件發(fā)生在DOM元素上時(shí)耙替,該事件并不完全發(fā)生在那個(gè)元素上。在冒泡階段曹体,事件冒泡俗扇,或者事件會向上傳播,發(fā)生在它的父代混坞,祖父母狐援,祖父母的父代钢坦,直到到達(dá)window為止究孕。

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

<script>
function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});
</script>

addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false爹凹,事件將在冒泡階段中發(fā)生厨诸,如果為true,則事件將在捕獲階段中發(fā)生禾酱。如果單擊child元素微酬,它將分別在控制臺上記錄child,parent颤陶,grandparent颗管,html,document和window滓走,這就是事件冒泡垦江。

8. 什么是事件捕獲?

當(dāng)事件發(fā)生在 DOM 元素上時(shí)搅方,該事件并不完全發(fā)生在那個(gè)元素上比吭。在捕獲階段,事件從window開始姨涡,一直到觸發(fā)事件的元素衩藤。

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

<script>
function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});
</script>

addEventListener方法具有第三個(gè)可選參數(shù)useCapture,其默認(rèn)值為false涛漂,事件將在冒泡階段中發(fā)生赏表,如果為true,則事件將在捕獲階段中發(fā)生匈仗。如果單擊child元素瓢剿,它將分別在控制臺上打印window,document锚沸,html跋选,grandparent和parent,這就是事件捕獲哗蜈。

9. event.preventDefault() 和 event.stopPropagation()方法之間有什么區(qū)別前标?

event.preventDefault() 方法可防止元素的默認(rèn)行為坠韩。如果在表單元素中使用,它將阻止其提交炼列。如果在錨元素中使用只搁,它將阻止其導(dǎo)航。如果在上下文菜單中使用俭尖,它將阻止其顯示或顯示氢惋。 event.stopPropagation()方法用于阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播。

10. 如何知道是否在元素中使用了event.preventDefault()方法稽犁?

我們可以在事件對象中使用event.defaultPrevented屬性焰望。它返回一個(gè)布爾值用來表明是否在特定元素中調(diào)用了event.preventDefault()。

11. 為什么此代碼 obj.someprop.x 會引發(fā)錯(cuò)誤?

const obj = {};
console.log(obj.someprop.x);

顯然已亥,由于我們嘗試訪問someprop屬性中的x屬性熊赖,而 someprop 并沒有在對象中,所以值為 undefined虑椎。記住對象本身不存在的屬性震鹉,并且其原型的默認(rèn)值為undefined。因?yàn)閡ndefined沒有屬性x捆姜,所以試圖訪問將會報(bào)錯(cuò)传趾。

12. 什么是 event.target ?

簡單來說泥技,event.target是發(fā)生事件的元素或觸發(fā)事件的元素浆兰。

假設(shè)有如下的 HTML 結(jié)構(gòu):

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
          <button style="margin:10px">
             Button
          </button>
        </div>
    </div>
 </div>
<script>
function clickFunc(event) {
  console.log(event.target);
}
</script>

如果單擊 button,即使我們將事件附加在最外面的div上零抬,它也將打印 button 標(biāo)簽镊讼,因此我們可以得出結(jié)論event.target是觸發(fā)事件的元素。

13. 什么是 event.currentTarget?

event.currentTarget是我們在其上顯式附加事件處理程序的元素平夜。

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
          <button style="margin:10px">
             Button
          </button>
        </div>
    </div>
 </div>
<script>
function clickFunc(event) {
  console.log(event.currentTarget);
}
</script>

如果單擊 button蝶棋,即使我們單擊該 button,它也會打印最外面的div標(biāo)簽忽妒。在此示例中玩裙,我們可以得出結(jié)論,event.currentTarget是附加事件處理程序的元素段直。

14. == 和 === 有什么區(qū)別吃溅?

==用于一般比較,===用于嚴(yán)格比較鸯檬,==在比較的時(shí)候可以轉(zhuǎn)換數(shù)據(jù)類型决侈,===嚴(yán)格比較,只要類型不匹配就返回flase喧务。

先來看看 == :

強(qiáng)制是將值轉(zhuǎn)換為另一種類型的過程赖歌。在這種情況下枉圃,==會執(zhí)行隱式強(qiáng)制。在比較兩個(gè)值之前庐冯,==需要執(zhí)行一些規(guī)則孽亲。

假設(shè)我們要比較x == y的值。

  • 如果x和y的類型相同展父,則 JS 會換成===操作符進(jìn)行比較返劲。

  • 如果x為null, y為undefined,則返回true栖茉。

  • 如果x為undefined且y為null篮绿,則返回true。

  • 如果x的類型是number, y的類型是string衡载,那么返回x == toNumber(y)搔耕。

  • 如果x的類型是string, y的類型是number隙袁,那么返回toNumber(x) == y痰娱。

  • 如果x為類型是boolean,則返回toNumber(x)== y菩收。

  • 如果y為類型是boolean梨睁,則返回x == toNumber(y)。

  • 如果x是string娜饵、symbol或number坡贺,而y是object類型,則返回x == toPrimitive(y)箱舞。

  • 如果x是object遍坟,y是string,symbol 則返回toPrimitive(x) == y晴股。

  • 剩下的 返回 false

注意:toPrimitive首先在對象中使用valueOf方法愿伴,然后使用toString方法來獲取該對象的原始值。

舉個(gè)例子

x y x == y
5 5 true
1 '1' true
null undefined true
0 false true
'1,2' [1,2] true
'[object Object]' {} true

這些例子都返回true电湘。

第一個(gè)示例符合條件1隔节,因?yàn)閤和y具有相同的類型和值。

第二個(gè)示例符合條件4寂呛,在比較之前將y轉(zhuǎn)換為數(shù)字怎诫。

第三個(gè)例子符合條件2。

第四個(gè)例子符合條件7贷痪,因?yàn)閥是boolean類型幻妓。

第五個(gè)示例符合條件8。使用toString()方法將數(shù)組轉(zhuǎn)換為字符串劫拢,該方法返回1,2肉津。

最后一個(gè)示例符合條件8胖缤。使用toString()方法將對象轉(zhuǎn)換為字符串,該方法返回[object Object]阀圾。

x y x === y
5 5 true
1 '1' false
null undefined false
0 false false
'1,2' [1,2] false
'[object Object]' {} false

如果使用===運(yùn)算符哪廓,則第一個(gè)示例以外的所有比較將返回false,因?yàn)樗鼈兊念愋筒煌鹾妫谝粋€(gè)示例將返回true涡真,因?yàn)閮烧叩念愋秃椭迪嗤?/p>

15. 為什么在 JS 中比較兩個(gè)相似的對象時(shí)返回 false?

let a = { a: 1 };
let b = { a: 1 };
let c = a;

console.log(a === b); // 打印 false肾筐,即使它們有相同的屬性
console.log(a === c); // true

JS 以不同的方式比較對象和基本類型哆料。在基本類型中,JS 通過值對它們進(jìn)行比較吗铐,而在對象中东亦,JS 通過引用或存儲變量的內(nèi)存中的地址對它們進(jìn)行比較。這就是為什么第一個(gè)console.log語句返回false唬渗,而第二個(gè)console.log語句返回true典阵。a和c有相同的引用地址,而a和b沒有镊逝。

16. !! 運(yùn)算符能做什么壮啊?

!!運(yùn)算符可以將右側(cè)的值強(qiáng)制轉(zhuǎn)換為布爾值,這也是將值轉(zhuǎn)換為布爾值的一種簡單方法撑蒜。

console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.log(!![].length); // false

17. 如何在一行中計(jì)算多個(gè)表達(dá)式的值歹啼?

可以使用逗號運(yùn)算符在一行中計(jì)算多個(gè)表達(dá)式。它從左到右求值座菠,并返回右邊最后一個(gè)項(xiàng)目或最后一個(gè)操作數(shù)的值狸眼。

let x = 5;

x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);

function addFive(num) {
  return num + 5;
}

上面的結(jié)果最后得到x的值為27。首先浴滴,我們將x的值增加到6拓萌,然后調(diào)用函數(shù)addFive(6)并將6作為參數(shù)傳遞并將結(jié)果重新分配給x,此時(shí)x的值為11巡莹。之后司志,將x的當(dāng)前值乘以2并將其分配給x,x的更新值為22降宅。然后骂远,將x的當(dāng)前值減去5并將結(jié)果分配給x x更新后的值為17。最后腰根,我們將x的值增加10激才,然后將更新的值分配給x,最終x的值為27。

18. 什么是變量提升瘸恼?

變量提升是用來描述變量和函數(shù)移動到其(全局或函數(shù))作用域頂部的術(shù)語劣挫。

為了理解變量提升,需要來了解一下執(zhí)行上下文东帅。執(zhí)行上下文是當(dāng)前正在執(zhí)行的“代碼環(huán)境”压固。執(zhí)行上下文有兩個(gè)階段:編譯和執(zhí)行。

編譯:在此階段靠闭,JS 引薦獲取所有函數(shù)聲明并將其提升到其作用域的頂部帐我,以便我們稍后可以引用它們并獲取所有變量聲明(使用var關(guān)鍵字進(jìn)行聲明),還會為它們提供默認(rèn)值: undefined愧膀。

執(zhí)行:在這個(gè)階段中拦键,它將值賦給之前提升的變量,并執(zhí)行或調(diào)用函數(shù)(對象中的方法)檩淋。

注意:只有使用var聲明的變量芬为,或者函數(shù)聲明才會被提升,相反蟀悦,函數(shù)表達(dá)式或箭頭函數(shù)媚朦,let和const聲明的變量,這些都不會被提升熬芜。

假設(shè)在全局使用域莲镣,有如下的代碼:

console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));

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

var y;

上面分別打印:undefined,1, Hello Mark!涎拉。

上面代碼在編譯階段其實(shí)是這樣的:

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

var y; // 默認(rèn)值 undefined

// 等待“編譯”階段完成,然后開始“執(zhí)行”階段

/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*/

編譯階段完成后的圆,它將啟動執(zhí)行階段調(diào)用方法鼓拧,并將值分配給變量。

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

var y;

//start "execution" phase

console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));

什么是作用域越妈?

JavaScript 中的作用域是我們可以有效訪問變量或函數(shù)的區(qū)域季俩。JS 有三種類型的作用域:全局作用域、函數(shù)作用域和塊作用域(ES6)梅掠。

  • 全局作用域:在全局命名空間中聲明的變量或函數(shù)位于全局作用域中酌住,因此在代碼中的任何地方都可以訪問它們。
var g = "global";

function globalFunc(){
  function innerFunc(){
    console.log(g); // global
  }
 innerFunc();
}
  • 函數(shù)作用域:在函數(shù)中聲明的變量阎抒、函數(shù)和參數(shù)可以在函數(shù)內(nèi)部訪問酪我,但不能在函數(shù)外部訪問。
function myFavoriteFunc(a) {
  if (true) {
    var b = "Hello " + a;
  }
  return b;
}

myFavoriteFunc("World");

console.log(a); // a is not defined
console.log(b); // b is not defined
  • 塊作用域:在塊{}中聲明的變量(let且叁,const)只能在其中訪問都哭。
 function testBlock(){
   if(true){
     let z = 5;
   }
   return z;
 }

 testBlock(); // z is not defined

作用域也是一組用于查找變量的規(guī)則。如果變量在當(dāng)前作用域中不存在,它將向外部作用域中查找并搜索欺矫,如果該變量不存在纱新,它將再次查找直到到達(dá)全局作用域,如果找到穆趴,則可以使用它脸爱,否則引發(fā)錯(cuò)誤,這種查找過程也稱為作用域鏈未妹。

/* 作用域鏈
     內(nèi)部作用域->外部作用域-> 全局作用域
*/

  // 全局作用域
  var variable1 = "Comrades";
  var variable2 = "Sayonara";

  function outer(){
  // 外部作用域
    var variable1 = "World";
    function inner(){
    // 內(nèi)部作用域
      var variable2 = "Hello";
      console.log(variable2 + " " + variable1);
    }
    inner();
  }
  outer(); // Hello World

20. 什么是閉包阅羹?

這可能是所有問題中最難的一個(gè)問題,因?yàn)殚]包是一個(gè)有爭議的話題教寂,這里從個(gè)人角度來談?wù)勀笥悖绻煌缀觯喽嗪:?/p>

閉包就是一個(gè)函數(shù)在聲明時(shí)能夠記住當(dāng)前作用域甥角、父函數(shù)作用域、及父函數(shù)作用域上的變量和參數(shù)的引用控妻,直至通過作用域鏈上全局作用域迂烁,基本上閉包是在聲明函數(shù)時(shí)創(chuàng)建的作用域看尼。

看看小例子:

   // 全局作用域
   var globalVar = "abc";

   function a(){
     console.log(globalVar);
   }

   a(); // "abc"

在此示例中,當(dāng)我們聲明a函數(shù)時(shí)盟步,全局作用域是a閉包的一部分藏斩。

來看一個(gè)更復(fù)雜的例子:

var globalVar = "global";
var outerVar = "outer"

function outerFunc(outerParam) {
  function innerFunc(innerParam) {
    console.log(globalVar, outerParam, innerParam);
  }
  return innerFunc;
}

const x = outerFunc(outerVar);
outerVar = "outer-2";
globalVar = "guess"
x("inner");

上面打印結(jié)果是 guess outer inner。

當(dāng)我們調(diào)用outerFunc函數(shù)并將返回值innerFunc函數(shù)分配給變量x時(shí)却盘,即使我們?yōu)閛uterVar變量分配了新值outer-2狰域,outerParam也繼續(xù)保留outer值,因?yàn)橹匦路峙涫窃谡{(diào)用outerFunc之后發(fā)生的黄橘,并且當(dāng)我們調(diào)用outerFunc函數(shù)時(shí)兆览,它會在作用域鏈中查找outerVar的值,此時(shí)的outerVar的值將為 "outer"塞关。

現(xiàn)在抬探,當(dāng)我們調(diào)用引用了innerFunc的x變量時(shí),innerParam將具有一個(gè)inner值帆赢,因?yàn)檫@是我們在調(diào)用中傳遞的值小压,而globalVar變量值為guess,因?yàn)樵谡{(diào)用x變量之前椰于,我們將一個(gè)新值分配給globalVar怠益。

下面這個(gè)示例演示沒有理解好閉包所犯的錯(cuò)誤:

const arrFuncs = [];
for(var i = 0; i < 5; i++){
  arrFuncs.push(function (){
    return i;
  });
}
console.log(i); // i is 5

for (let i = 0; i < arrFuncs.length; i++) {
  console.log(arrFuncs[i]()); // 都打印 5
}

由于閉包,此代碼無法正常運(yùn)行廉羔。var關(guān)鍵字創(chuàng)建一個(gè)全局變量溉痢,當(dāng)我們 push 一個(gè)函數(shù)時(shí)僻造,這里返回的全局變量i。因此孩饼,當(dāng)我們在循環(huán)后在該數(shù)組中調(diào)用其中一個(gè)函數(shù)時(shí)髓削,它會打印5,因?yàn)槲覀兊玫絠的當(dāng)前值為5镀娶,我們可以訪問它立膛,因?yàn)樗侨肿兞俊?/p>

因?yàn)殚]包在創(chuàng)建變量時(shí)會保留該變量的引用而不是其值。我們可以使用IIFES或使用 let 來代替 var 的聲明梯码。

21. JavaScript 中的虛值是什么宝泵?

const falsyValues = ['', 0, null, undefined, NaN, false];

簡單的來說虛值就是是在轉(zhuǎn)換為布爾值時(shí)變?yōu)?false 的值。

22. 如何檢查值是否虛值轩娶?

使用 Boolean 函數(shù)或者 !! 運(yùn)算符儿奶。

23. 'use strict' 是干嘛用的?

"use strict" 是 ES5 特性鳄抒,它使我們的代碼在函數(shù)或整個(gè)腳本中處于嚴(yán)格模式闯捎。嚴(yán)格模式幫助我們在代碼的早期避免 bug,并為其添加限制许溅。

嚴(yán)格模式的一些限制:

  • 變量必須聲明后再使用

  • 函數(shù)的參數(shù)不能有同名屬性瓤鼻,否則報(bào)錯(cuò)

  • 不能使用with語句

  • 不能對只讀屬性賦值,否則報(bào)錯(cuò)

  • 不能使用前綴 0 表示八進(jìn)制數(shù)贤重,否則報(bào)錯(cuò)

  • 不能刪除不可刪除的屬性茬祷,否則報(bào)錯(cuò)

  • 不能刪除變量delete prop,會報(bào)錯(cuò)并蝗,只能刪除屬性delete global[prop]

  • eval不能在它的外層作用域引入變量

  • eval和arguments不能被重新賦值

  • arguments不會自動反映函數(shù)參數(shù)的變化

  • 不能使用arguments.callee

  • 不能使用arguments.caller

  • 禁止this指向全局對象

  • 不能使用fn.caller和fn.arguments獲取函數(shù)調(diào)用的堆棧

  • 增加了保留字(比如protected祭犯、static和interface)

設(shè)立”嚴(yán)格模式”的目的,主要有以下幾個(gè):

  • 消除Javascript語法的一些不合理借卧、不嚴(yán)謹(jǐn)之處盹憎,減少一些怪異行為;

  • 消除代碼運(yùn)行的一些不安全之處,保證代碼運(yùn)行的安全铐刘;

  • 提高編譯器效率,增加運(yùn)行速度影晓;

  • 為未來新版本的Javascript做好鋪墊镰吵。

24. JavaScript 中 this 值是什么?

基本上挂签,this指的是當(dāng)前正在執(zhí)行或調(diào)用該函數(shù)的對象的值疤祭。this值的變化取決于我們使用它的上下文和我們在哪里使用它。

const carDetails = {
  name: "Ford Mustang",
  yearBought: 2005,
  getName(){
    return this.name;
  },
  isRegistered: true
};

console.log(carDetails.getName()); // Ford Mustang

這通常是我們期望的結(jié)果饵婆,因?yàn)樵趃etName方法中我們返回this.name勺馆,在此上下文中,this指向的是carDetails對象,該對象當(dāng)前是執(zhí)行函數(shù)的“所有者”對象草穆。

接下我們做些奇怪的事情:

var name = "Ford Ranger";
var getCarName = carDetails.getName;

console.log(getCarName()); // Ford Ranger

上面打印Ford Ranger灌灾,這很奇怪,因?yàn)樵诘谝粋€(gè)console.log語句中打印的是Ford Mustang悲柱。這樣做的原因是getCarName方法有一個(gè)不同的“所有者”對象锋喜,即window對象。在全局作用域中使用var關(guān)鍵字聲明變量會在window對象中附加與變量名稱相同的屬性豌鸡。請記住嘿般,當(dāng)沒有使用“use strict”時(shí),在全局作用域中this指的是window對象涯冠。

console.log(getCarName === window.getCarName); // true
console.log(getCarName === this.getCarName); // true

本例中的this和window引用同一個(gè)對象炉奴。

解決這個(gè)問題的一種方法是在函數(shù)中使用apply和call方法。

console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails));  // Ford Mustang

apply和call方法期望第一個(gè)參數(shù)是一個(gè)對象蛇更,該對象是函數(shù)內(nèi)部this的值瞻赶。

IIFE或立即執(zhí)行的函數(shù)表達(dá)式,在全局作用域內(nèi)聲明的函數(shù)械荷,對象內(nèi)部方法中的匿名函數(shù)和內(nèi)部函數(shù)的this具有默認(rèn)值共耍,該值指向window對象。

   (function (){
     console.log(this);
   })(); // 打印 "window" 對象

   function iHateThis(){
      console.log(this);
   }

   iHateThis(); // 打印 "window" 對象

   const myFavoriteObj = {
     guessThis(){
        function getName(){
          console.log(this.name);
        }
        getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
     }
   };


   myFavoriteObj.guessThis(); // 打印 "window" 對象
   myFavoriteObj.thisIsAnnoying(function (){
     console.log(this); // 打印 "window" 對象
   });
   

如果我們要獲取myFavoriteObj對象中的name屬性(即Marko Polo)的值吨瞎,則有兩種方法可以解決此問題痹兜。

一種是將 this 值保存在變量中。

const myFavoriteObj = {
 guessThis(){
  const self = this; // 把 this 值保存在 self 變量中
  function getName(){
    console.log(self.name);
  }
  getName();
 },
 name: 'Marko Polo',
 thisIsAnnoying(callback){
   callback();
  }
};

第二種方式是使用箭頭函數(shù)

   const myFavoriteObj = {
     guessThis(){
         const getName = () => {
           //copies the value of "this" outside of this arrow function
           console.log(this.name);
         }
         getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
     }
   };

箭頭函數(shù)沒有自己的 this颤诀。它復(fù)制了這個(gè)封閉的詞法作用域中this值字旭,在這個(gè)例子中,this值在getName內(nèi)部函數(shù)之外崖叫,也就是myFavoriteObj對象遗淳。

25. 對象的 prototype(原型) 是什么?

簡單地說心傀,原型就是對象的藍(lán)圖屈暗。如果它存在當(dāng)前對象中,則將其用作屬性和方法的回退脂男。它是在對象之間共享屬性和功能的方法养叛,這也是JavaScript實(shí)現(xiàn)繼承的核心。

const o = {};
console.log(o.toString()); // [object Object]

即使o對象中不存在o.toString方法宰翅,它也不會引發(fā)錯(cuò)誤弃甥,而是返回字符串[object Object]。當(dāng)對象中不存在屬性時(shí)汁讼,它將查看其原型淆攻,如果仍然不存在阔墩,則將其查找到原型的原型,依此類推瓶珊,直到在原型鏈中找到具有相同屬性的屬性為止啸箫。原型鏈的末尾是Object.prototype。

console.log(o.toString === Object.prototype.toString); // true

26. 什么是 IIFE艰毒,它的用途是什么筐高?

IIFE或立即調(diào)用的函數(shù)表達(dá)式,是在創(chuàng)建或聲明后將被調(diào)用或執(zhí)行的函數(shù)丑瞧。創(chuàng)建IIFE的語法是柑土,將function (){}包裹在在括號()內(nèi),然后再用另一個(gè)括號()調(diào)用它绊汹,如:(function(){})()

(function(){
  ...
} ());

(function () {
  ...
})();

(function named(params) {
  ...
})();

(() => {

});

(function (global) {
  ...
})(window);

const utility = (function () {
  return {
    ...
  }
})

這些示例都是有效的IIFE稽屏。倒數(shù)第二個(gè)救命表明我們可以將參數(shù)傳遞給IIFE函數(shù)。最后一個(gè)示例表明西乖,我們可以將IIFE的結(jié)果保存到變量中狐榔,以便稍后使用。

IIFE的一個(gè)主要作用是避免與全局作用域內(nèi)的其他變量命名沖突或污染全局命名空間获雕,來個(gè)例子薄腻。

<script src="https://cdnurl.com/somelibrary.js"></script>

假設(shè)我們引入了一個(gè)somelibr.js的鏈接,它提供了一些我們在代碼中使用的全局函數(shù)届案,但是這個(gè)庫有兩個(gè)方法我們沒有使用:createGraph和drawGraph庵楷,因?yàn)檫@些方法都有bug。我們想實(shí)現(xiàn)自己的createGraph和drawGraph方法楣颠。

解決此問題的一種方法是直接覆蓋:

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function createGraph() {
      // createGraph logic here
   }
   function drawGraph() {
      // drawGraph logic here
   }
</script>

當(dāng)我們使用這個(gè)解決方案時(shí)尽纽,我們覆蓋了庫提供給我們的那兩個(gè)方法。

另一種方式是我們自己改名稱:

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function myCreateGraph() {
      // createGraph logic here
   }
   function myDrawGraph() {
      // drawGraph logic here
   }
</script>

當(dāng)我們使用這個(gè)解決方案時(shí)童漩,我們把那些函數(shù)調(diào)用更改為新的函數(shù)名弄贿。

還有一種方法就是使用IIFE:

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   const graphUtility = (function () {
      function createGraph() {
         // createGraph logic here
      }
      function drawGraph() {
         // drawGraph logic here
      }
      return {
         createGraph,
         drawGraph
      }
   })
</script>

在此解決方案中,我們要聲明了graphUtility 變量矫膨,用來保存IIFE執(zhí)行的結(jié)果差凹,該函數(shù)返回一個(gè)包含兩個(gè)方法createGraph和drawGraph的對象。

IIFE 還可以用來解決一個(gè)常見的面試題:

var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
   li[i].addEventListener('click', function (e) {
      console.log(i);
})

假設(shè)我們有一個(gè)帶有l(wèi)ist-group類的ul元素侧馅,它有5個(gè)li子元素直奋。當(dāng)我們單擊單個(gè)li元素時(shí),打印對應(yīng)的下標(biāo)值施禾。但在此外上述代碼不起作用,這里每次點(diǎn)擊 li 打印 i 的值都是5搁胆,這是由于閉包的原因弥搞。

閉包只是函數(shù)記住其當(dāng)前作用域邮绿,父函數(shù)作用域和全局作用域的變量引用的能力。當(dāng)我們在全局作用域內(nèi)使用var關(guān)鍵字聲明變量時(shí)攀例,就創(chuàng)建全局變量i船逮。因此,當(dāng)我們單擊li元素時(shí)粤铭,它將打印5挖胃,因?yàn)檫@是稍后在回調(diào)函數(shù)中引用它時(shí)i的值。

使用 IIFE 可以解決此問題:

var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
   (function (currentIndex) {
      li[currentIndex].addEventListener('click', function (e) {
         console.log(currentIndex);
      })
   })(i);
}

該解決方案之所以行的通梆惯,是因?yàn)镮IFE會為每次迭代創(chuàng)建一個(gè)新的作用域酱鸭,我們捕獲i的值并將其傳遞給currentIndex參數(shù),因此調(diào)用IIFE時(shí)垛吗,每次迭代的currentIndex值都是不同的凹髓。

27. Function.prototype.apply 方法的用途是什么?

apply() 方法調(diào)用一個(gè)具有給定this值的函數(shù)怯屉,以及作為一個(gè)數(shù)組(或類似數(shù)組對象)提供的參數(shù)蔚舀。

const details = {
  message: 'Hello World!'
};

function getMessage(){
  return this.message;
}

getMessage.apply(details); // 'Hello World!'

call()方法的作用和 apply() 方法類似,區(qū)別就是call()方法接受的是參數(shù)列表锨络,而apply()方法接受的是一個(gè)參數(shù)數(shù)組赌躺。

const person = {
  name: "Marko Polo"
};

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`;
}

greeting.apply(person, ['Hello']); // "Hello Marko Polo!"

28. Function.prototype.call 方法的用途是什么?

call() 方法使用一個(gè)指定的 this 值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來調(diào)用一個(gè)函數(shù)羡儿。

const details = {
  message: 'Hello World!'
};

function getMessage(){
  return this.message;
}

getMessage.call(details); // 'Hello World!'

注意:該方法的語法和作用與 apply() 方法類似礼患,只有一個(gè)區(qū)別,就是 call() 方法接受的是一個(gè)參數(shù)列表失受,而 apply() 方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組讶泰。

const person = {
  name: "Marko Polo"
};

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`;
}

greeting.call(person, 'Hello'); // "Hello Marko Polo!"

29. Function.prototype.apply 和 Function.prototype.call 之間有什么區(qū)別?

apply()方法可以在使用一個(gè)指定的 this 值和一個(gè)參數(shù)數(shù)組(或類數(shù)組對象)的前提下調(diào)用某個(gè)函數(shù)或方法拂到。call()方法類似于apply()痪署,不同之處僅僅是call()接受的參數(shù)是參數(shù)列表。

const obj1 = {
 result:0
};

const obj2 = {
 result:0
};

function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}

reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15

30. Function.prototype.bind 的用途是什么兄旬?

bind() 方法創(chuàng)建一個(gè)新的函數(shù)狼犯,在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù)领铐,而其余參數(shù)將作為新函數(shù)的參數(shù)悯森,供調(diào)用時(shí)使用。

import React from 'react';

class MyComponent extends React.Component {
     constructor(props){
          super(props);
          this.state = {
             value : ""
          }
          this.handleChange = this.handleChange.bind(this);
          // 將 “handleChange” 方法綁定到 “MyComponent” 組件
     }

     handleChange(e){
       //do something amazing here
     }

     render(){
        return (
              <>
                <input type={this.props.type}
                        value={this.state.value}
                     onChange={this.handleChange}
                  />
              </>
        )
     }
}

31. 什么是函數(shù)式編程? JavaScript 的哪些特性使其成為函數(shù)式語言的候選語言绪撵?

函數(shù)式編程(通称耙觯縮寫為FP)是通過編寫純函數(shù),避免共享狀態(tài)音诈、可變數(shù)據(jù)幻碱、副作用 來構(gòu)建軟件的過程绎狭。數(shù)式編程是聲明式 的而不是命令式 的,應(yīng)用程序的狀態(tài)是通過純函數(shù)流動的褥傍。與面向?qū)ο缶幊绦纬蓪Ρ壤芩唬嫦驅(qū)ο笾袘?yīng)用程序的狀態(tài)通常與對象中的方法共享和共處。

函數(shù)式編程是一種編程范式 恍风,這意味著它是一種基于一些基本的定義原則(如上所列)思考軟件構(gòu)建的方式蹦狂。當(dāng)然,編程范示的其他示例也包括面向?qū)ο缶幊毯瓦^程編程朋贬。

函數(shù)式的代碼往往比命令式或面向?qū)ο蟮拇a更簡潔凯楔,更可預(yù)測,更容易測試 - 但如果不熟悉它以及與之相關(guān)的常見模式兄世,函數(shù)式的代碼也可能看起來更密集雜亂啼辣,并且 相關(guān)文獻(xiàn)對新人來說是不好理解的。

JavaScript支持閉包和高階函數(shù)是函數(shù)式編程語言的特點(diǎn)御滩。

32. 什么是高階函數(shù)鸥拧?

高階函數(shù)只是將函數(shù)作為參數(shù)或返回值的函數(shù)。

function higherOrderFunction(param,callback){
    return callback(param);
}

33. 為什么函數(shù)被稱為一等公民削解?

在JavaScript中富弦,函數(shù)不僅擁有一切傳統(tǒng)函數(shù)的使用方式(聲明和調(diào)用),而且可以做到像簡單值一樣賦值(var func = function(){})氛驮、傳參(function func(x,callback){callback();})腕柜、返回(function(){return function(){}}),這樣的函數(shù)也稱之為第一級函數(shù)(First-class Function)矫废。不僅如此盏缤,JavaScript中的函數(shù)還充當(dāng)了類的構(gòu)造函數(shù)的作用,同時(shí)又是一個(gè)Function類的實(shí)例(instance)蓖扑。這樣的多重身份讓JavaScript的函數(shù)變得非常重要唉铜。

34. 手動實(shí)現(xiàn) Array.prototype.map 方法

map() 方法創(chuàng)建一個(gè)新數(shù)組,其結(jié)果是該數(shù)組中的每個(gè)元素都調(diào)用一個(gè)提供的函數(shù)后返回的結(jié)果律杠。

function map(arr, mapCallback) {
  // 首先潭流,檢查傳遞的參數(shù)是否正確。
  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
    return [];
  } else {
    let result = [];
    // 每次調(diào)用此函數(shù)時(shí)柜去,我們都會創(chuàng)建一個(gè) result 數(shù)組
    // 因?yàn)槲覀儾幌敫淖冊紨?shù)組灰嫉。
    for (let i = 0, len = arr.length; i < len; i++) {
      result.push(mapCallback(arr[i], i, arr));
      // 將 mapCallback 返回的結(jié)果 push 到 result 數(shù)組中
    }
    return result;
  }
}

35. 手動實(shí)現(xiàn)Array.prototype.filter方法

filter() 方法創(chuàng)建一個(gè)新數(shù)組, 其包含通過所提供函數(shù)實(shí)現(xiàn)的測試的所有元素。

function filter(arr, filterCallback) {
  // 首先嗓奢,檢查傳遞的參數(shù)是否正確讼撒。
  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
  {
    return [];
  } else {
    let result = [];
     // 每次調(diào)用此函數(shù)時(shí),我們都會創(chuàng)建一個(gè) result 數(shù)組
     // 因?yàn)槲覀儾幌敫淖冊紨?shù)組。
    for (let i = 0, len = arr.length; i < len; i++) {
      // 檢查 filterCallback 的返回值是否是真值
      if (filterCallback(arr[i], i, arr)) {
      // 如果條件為真椿肩,則將數(shù)組元素 push 到 result 中
        result.push(arr[i]);
      }
    }
    return result; // return the result array
  }
}

36. 手動實(shí)現(xiàn)Array.prototype.reduce方法

reduce() 方法對數(shù)組中的每個(gè)元素執(zhí)行一個(gè)由您提供的reducer函數(shù)(升序執(zhí)行)瞻颂,將其結(jié)果匯總為單個(gè)返回值。

function reduce(arr, reduceCallback, initialValue) {
  // 首先郑象,檢查傳遞的參數(shù)是否正確。
  if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
  {
    return [];
  } else {
    // 如果沒有將initialValue傳遞給該函數(shù)茬末,我們將使用第一個(gè)數(shù)組項(xiàng)作為initialValue
    let hasInitialValue = initialValue !== undefined;
    let value = hasInitialValue ? initialValue : arr[0];
   厂榛、

    // 如果有傳遞 initialValue,則索引從 1 開始丽惭,否則從 0 開始
    for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
      value = reduceCallback(value, arr[i], i, arr);
    }
    return value;
  }
}

37. arguments 的對象是什么击奶?

arguments對象是函數(shù)中傳遞的參數(shù)值的集合。它是一個(gè)類似數(shù)組的對象责掏,因?yàn)樗幸粋€(gè)length屬性柜砾,我們可以使用數(shù)組索引表示法arguments[1]來訪問單個(gè)值,但它沒有數(shù)組中的內(nèi)置方法换衬,如:forEach痰驱、reduce、filter和map瞳浦。

我們可以使用Array.prototype.slice將arguments對象轉(zhuǎn)換成一個(gè)數(shù)組担映。

function one() {
  return Array.prototype.slice.call(arguments);
}

注意:箭頭函數(shù)中沒有arguments對象。

function one() {
  return arguments;
}
const two = function () {
  return arguments;
}
const three = function three() {
  return arguments;
}

const four = () => arguments;

four(); // Throws an error  - arguments is not defined

當(dāng)我們調(diào)用函數(shù)four時(shí)叫潦,它會拋出一個(gè)ReferenceError: arguments is not defined error蝇完。使用rest語法,可以解決這個(gè)問題矗蕊。

const four = (...args) => args;

這會自動將所有參數(shù)值放入數(shù)組中短蜕。

38. 如何創(chuàng)建一個(gè)沒有 prototype(原型)的對象?

我們可以使用Object.create方法創(chuàng)建沒有原型的對象傻咖。

const o1 = {};
console.log(o1.toString()); // [object Object]

const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function

39. 為什么在調(diào)用這個(gè)函數(shù)時(shí)朋魔,代碼中的b會變成一個(gè)全局變量?

function myFunc() {
  let a = b = 0;
}

myFunc();

原因是賦值運(yùn)算符是從右到左的求值的。這意味著當(dāng)多個(gè)賦值運(yùn)算符出現(xiàn)在一個(gè)表達(dá)式中時(shí)没龙,它們是從右向左求值的铺厨。所以上面代碼變成了這樣:

function myFunc() {
  let a = (b = 0);
}

myFunc();

首先,表達(dá)式b = 0求值硬纤,在本例中b沒有聲明解滓。因此,JS引擎在這個(gè)函數(shù)外創(chuàng)建了一個(gè)全局變量b筝家,之后表達(dá)式b = 0的返回值為0洼裤,并賦給新的局部變量a。

我們可以通過在賦值之前先聲明變量來解決這個(gè)問題溪王。

function myFunc() {
  let a,b;
  a = b = 0;
}
myFunc();

40. ECMAScript 是什么腮鞍?

ECMAScript 是編寫腳本語言的標(biāo)準(zhǔn)值骇,這意味著JavaScript遵循ECMAScript標(biāo)準(zhǔn)中的規(guī)范變化,因?yàn)樗荍avaScript的藍(lán)圖移国。

ECMAScript 和 Javascript吱瘩,本質(zhì)上都跟一門語言有關(guān),一個(gè)是語言本身的名字迹缀,一個(gè)是語言的約束條件
只不過發(fā)明JavaScript的那個(gè)人(Netscape公司)使碾,把東西交給了ECMA(European Computer Manufacturers Association),這個(gè)人規(guī)定一下他的標(biāo)準(zhǔn)祝懂,因?yàn)楫?dāng)時(shí)有java語言了票摇,又想強(qiáng)調(diào)這個(gè)東西是讓ECMA這個(gè)人定的規(guī)則,所以就這樣一個(gè)神奇的東西誕生了砚蓬,這個(gè)東西的名稱就叫做ECMAScript矢门。

javaScript = ECMAScript + DOM + BOM(自認(rèn)為是一種廣義的JavaScript)

ECMAScript說什么JavaScript就得做什么!

JavaScript(狹義的JavaScript)做什么都要問問ECMAScript我能不能這樣干灰蛙!如果不能我就錯(cuò)了祟剔!能我就是對的!

突然感覺JavaScript好沒有尊嚴(yán)缕允,為啥要搞個(gè)人出來約束自己峡扩,

那個(gè)人被創(chuàng)造出來也好委屈,自己被創(chuàng)造出來完全是因?yàn)橐s束JavaScript障本。

41. ES6或ECMAScript 2015有哪些新特性教届?

  • 箭頭函數(shù)
  • 模板字符串
  • 加強(qiáng)的對象字面量
  • 對象解構(gòu)
  • Promise
  • 生成器
  • 模塊
  • Symbol
  • 代理
  • Set
  • 函數(shù)默認(rèn)參數(shù)
  • rest 和展開
  • 塊作用域

42. 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已聲明還沒賦值,默認(rèn)得到undefined值
var a = 100;

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

console.log(c); // 報(bào)錯(cuò):c is not defined  ===> 找不到c這個(gè)變量
const c = 10;

let和const聲明形成塊作用域

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

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

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

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

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

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

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

//  控制臺報(bào)錯(cuò):Identifier 'a' has already been declared  ===> 標(biāo)識符a已經(jīng)被聲明了潦刃。

暫存死區(qū)

var a = 100;

if(1){
    a = 10;
    //在當(dāng)前塊作用域中存在a使用let/const聲明的情況下融求,給a賦值10時(shí)窃这,只會在當(dāng)前作用域找變量a钧唐,
    // 而這時(shí),還未到聲明時(shí)候寞酿,所以控制臺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'}

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

箭頭函數(shù)表達(dá)式的語法比函數(shù)表達(dá)式更簡潔煌茴,并且沒有自己的this随闺,arguments,super或new.target蔓腐。箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方矩乐,并且它不能用作構(gòu)造函數(shù)。

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

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

在本例中合住,ES5 版本中有function(){}聲明和return關(guān)鍵字绰精,這兩個(gè)關(guān)鍵字分別是創(chuàng)建函數(shù)和返回值所需要的。在箭頭函數(shù)版本中透葛,我們只需要()括號,不需要 return 語句卿樱,因?yàn)槿绻覀冎挥幸粋€(gè)表達(dá)式或值需要返回僚害,箭頭函數(shù)就會有一個(gè)隱式的返回。

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

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

我們還可以在箭頭函數(shù)中使用與函數(shù)表達(dá)式和函數(shù)聲明相同的參數(shù)繁调。如果我們在一個(gè)箭頭函數(shù)中有一個(gè)參數(shù)萨蚕,則可以省略括號。

const getArgs = () => arguments

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

箭頭函數(shù)不能訪問arguments對象蹄胰。所以調(diào)用第一個(gè)getArgs函數(shù)會拋出一個(gè)錯(cuò)誤岳遥。相反,我們可以使用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 對象庆猫。

44. 什么是類认轨?

類(class)是在 JS 中編寫構(gòu)造函數(shù)的新方法。它是使用構(gòu)造函數(shù)的語法糖月培,在底層中使用仍然是原型和基于原型的繼承嘁字。

   //ES5 Version
   function Person(firstName, lastName, age, address){
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
      this.address = address;
   }

   Person.self = function(){
     return this;
   }

   Person.prototype.toString = function(){
     return "[object Person]";
   }

   Person.prototype.getFullName = function (){
     return this.firstName + " " + this.lastName;
   }

   //ES6 Version
   class Person {
        constructor(firstName, lastName, age, address){
            this.lastName = lastName;
            this.firstName = firstName;
            this.age = age;
            this.address = address;
        }

        static self() {
           return this;
        }

        toString(){
           return "[object Person]";
        }

        getFullName(){
           return `${this.firstName} ${this.lastName}`;
        }
   }

重寫方法并從另一個(gè)類繼承。

//ES5 Version
Employee.prototype = Object.create(Person.prototype);

function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
  Person.call(this, firstName, lastName, age, address);
  this.jobTitle = jobTitle;
  this.yearStarted = yearStarted;
}

Employee.prototype.describe = function () {
  return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
   and I started at ${this.yearStarted}`;
}

Employee.prototype.toString = function () {
  return "[object Employee]";
}

//ES6 Version
class Employee extends Person { //Inherits from "Person" class
  constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
    super(firstName, lastName, age, address);
    this.jobTitle = jobTitle;
    this.yearStarted = yearStarted;
  }

  describe() {
    return `I am ${this.getFullName()} and I have a position of ${this.jobTitle}
     and I started at ${this.yearStarted}`;
  }

  toString() { // Overriding the "toString" method of "Person"
    return "[object Employee]";
  }
}

所以我們要怎么知道它在內(nèi)部使用原型杉畜?

class Something {

}

function AnotherSomething(){

}
const as = new AnotherSomething();
const s = new Something();

console.log(typeof Something); // "function"
console.log(typeof AnotherSomething); // "function"
console.log(as.toString()); // "[object Object]"
console.log(as.toString()); // "[object Object]"
console.log(as.toString === Object.prototype.toString); // true
console.log(s.toString === Object.prototype.toString); // true

45. 什么是模板字符串纪蜒?

模板字符串是在 JS 中創(chuàng)建字符串的一種新方法。我們可以通過使用反引號使模板字符串化寻行。

//ES5 Version
var greet = 'Hi I\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;

在 ES5 中我們需要使用一些轉(zhuǎn)義字符來達(dá)到多行的效果霍掺,在模板字符串不需要這么麻煩:

//ES5 Version
var lastWords = '\n'
  + '   I  \n'
  + '   Am  \n'
  + 'Iron Man \n';


//ES6 Version
let lastWords = `
    I
    Am
  Iron Man
`;

在ES5版本中,我們需要添加\n以在字符串中添加新行。在模板字符串中杆烁,我們不需要這樣做牙丽。

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


//ES6 Version
function greet(name) {
  return `Hello ${name} !`;
}

在 ES5 版本中,如果需要在字符串中添加表達(dá)式或值兔魂,則需要使用+運(yùn)算符烤芦。在模板字符串s中,我們可以使用${expr}嵌入一個(gè)表達(dá)式析校,這使其比 ES5 版本更整潔构罗。

46. 什么是對象解構(gòu)?

對象析構(gòu)是從對象或數(shù)組中獲取或提取值的一種新的智玻、更簡潔的方法遂唧。假設(shè)有如下的對象:

const employee = {
  firstName: "Marko",
  lastName: "Polo",
  position: "Software Developer",
  yearHired: 2017
};

從對象獲取屬性,早期方法是創(chuàng)建一個(gè)與對象屬性同名的變量吊奢。這種方法很麻煩盖彭,因?yàn)槲覀円獮槊總€(gè)屬性創(chuàng)建一個(gè)新變量。假設(shè)我們有一個(gè)大對象页滚,它有很多屬性和方法召边,用這種方法提取屬性會很麻煩。

var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;

使用解構(gòu)方式語法就變得簡潔多了:

{ firstName, lastName, position, yearHired } = employee;

我們還可以為屬性取別名:

let { firstName: fName, lastName: lName, position, yearHired } = employee;

當(dāng)然如果屬性值為 undefined 時(shí)裹驰,我們還可以指定默認(rèn)值:

let { firstName = "Mark", lastName: lName, position, yearHired } = employee;

47. 什么是 ES6 模塊隧熙?

模塊使我們能夠?qū)⒋a基礎(chǔ)分割成多個(gè)文件,以獲得更高的可維護(hù)性幻林,并且避免將所有代碼放在一個(gè)大文件中贞盯。在 ES6 支持模塊之前,有兩個(gè)流行的模塊滋将。

  • CommonJS-Node.js
  • AMD(異步模塊定義)-瀏覽器

基本上邻悬,使用模塊的方式很簡單,import用于從另一個(gè)文件中獲取功能或幾個(gè)功能或值随闽,同時(shí)export用于從文件中公開功能或幾個(gè)功能或值父丰。

導(dǎo)出

使用 ES5 (CommonJS)

// 使用 ES5 CommonJS - helpers.js
exports.isNull = function (val) {
  return val === null;
}

exports.isUndefined = function (val) {
  return val === undefined;
}

exports.isNullOrUndefined = function (val) {
  return exports.isNull(val) || exports.isUndefined(val);
}

使用 ES6 模塊

// 使用 ES6 Modules - helpers.js
export function isNull(val){
  return val === null;
}

export function isUndefined(val) {
  return val === undefined;
}

export function isNullOrUndefined(val) {
  return isNull(val) || isUndefined(val);
}

在另一個(gè)文件中導(dǎo)入函數(shù)

// 使用 ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;

// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
-------------------------------------------------------

// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object

// or

import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';

// using "as" for renaming named exports

在文件中導(dǎo)出單個(gè)功能或默認(rèn)導(dǎo)出

使用 ES5 (CommonJS)

// 使用 ES5 (CommonJS) - index.js
class Helpers {
  static isNull(val) {
    return val === null;
  }

  static isUndefined(val) {
    return val === undefined;
  }

  static isNullOrUndefined(val) {
    return this.isNull(val) || this.isUndefined(val);
  }
}


module.exports = Helpers;

使用ES6 Modules

// 使用 ES6 Modules - helpers.js
class Helpers {
  static isNull(val) {
    return val === null;
  }

  static isUndefined(val) {
    return val === undefined;
  }

  static isNullOrUndefined(val) {
    return this.isNull(val) || this.isUndefined(val);
  }
}

export default Helpers

從另一個(gè)文件導(dǎo)入單個(gè)功能

使用ES5 (CommonJS)

// 使用 ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));

使用 ES6 Modules

import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));

48. 什么是Set對象,它是如何工作的掘宪?

Set 對象允許你存儲任何類型的唯一值蛾扇,無論是原始值或者是對象引用。

我們可以使用Set構(gòu)造函數(shù)創(chuàng)建Set實(shí)例魏滚。

const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);

我們可以使用add方法向Set實(shí)例中添加一個(gè)新值镀首,因?yàn)閍dd方法返回Set對象,所以我們可以以鏈?zhǔn)降姆绞皆俅问褂胊dd鼠次。如果一個(gè)值已經(jīng)存在于Set對象中更哄,那么它將不再被添加芋齿。

set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一個(gè)“k”不會被添加到set對象中,因?yàn)樗呀?jīng)存在了

我們可以使用has方法檢查Set實(shí)例中是否存在特定的值成翩。

set2.has("a") // true
set2.has("z") // true

我們可以使用size屬性獲得Set實(shí)例的長度觅捆。

set2.size // returns 10

可以使用clear方法刪除 Set 中的數(shù)據(jù)。

set2.clear();

我們可以使用Set對象來刪除數(shù)組中重復(fù)的元素麻敌。

const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8]

49. 什么是回調(diào)函數(shù)栅炒?

回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個(gè)參數(shù)傳遞給其他的代碼术羔,其作用是在需要的時(shí)候方便調(diào)用這段(回調(diào)函數(shù))代碼赢赊。

在JavaScript中函數(shù)也是對象的一種,同樣對象可以作為參數(shù)傳遞給函數(shù)级历,因此函數(shù)也可以作為參數(shù)傳遞給另外一個(gè)函數(shù)释移,這個(gè)作為參數(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ù)或事件添加一些功能皱坛。

數(shù)組中的reduce编曼、filter和map方法需要一個(gè)回調(diào)作為參數(shù)∈1伲回調(diào)的一個(gè)很好的類比是掐场,當(dāng)你打電話給某人,如果他們不接贩猎,你留下一條消息熊户,你期待他們回調(diào)。調(diào)用某人或留下消息的行為是事件或數(shù)據(jù)吭服,回調(diào)是你希望稍后發(fā)生的操作嚷堡。

50. Promise 是什么?

Promise 是異步編程的一種解決方案:從語法上講艇棕,promise是一個(gè)對象蝌戒,從它可以獲取異步操作的消息;從本意上講沼琉,它是承諾北苟,承諾它過一段時(shí)間會給你一個(gè)結(jié)果。promise有三種狀態(tài):pending(等待態(tài))打瘪,fulfiled(成功態(tài))友鼻,rejected(失敗態(tài))傻昙;狀態(tài)一旦改變,就不會再變彩扔。創(chuàng)造promise實(shí)例后妆档,它會立即執(zhí)行。

fs.readFile('somefile.txt', function (e, data) {
  if (e) {
    console.log(e);
  }
  console.log(data);
});

如果我們在回調(diào)內(nèi)部有另一個(gè)異步操作借杰,則此方法存在問題过吻。我們將有一個(gè)混亂且不可讀的代碼。此代碼稱為“回調(diào)地獄”蔗衡。

// 回調(diào)地獄
fs.readFile('somefile.txt', function (e, data) {
  //your code here
  fs.readdir('directory', function (e, files) {
    //your code here
    fs.mkdir('directory', function (e) {
      //your code here
    })
  })
})

如果我們在這段代碼中使用promise纤虽,它將更易于閱讀、理解和維護(hù)绞惦。

promReadFile('file/path')
  .then(data => {
    return promReaddir('directory');
  })
  .then(data => {
    return promMkdir('directory');
  })
  .catch(e => {
    console.log(e);
  })

promise有三種不同的狀態(tài):

  • pending:初始狀態(tài)逼纸,完成或失敗狀態(tài)的前一個(gè)狀態(tài)
  • fulfilled:操作成功完成
  • rejected:操作失敗

pending 狀態(tài)的 Promise 對象會觸發(fā) fulfilled/rejected 狀態(tài),在其狀態(tài)處理方法中可以傳入?yún)?shù)/失敗信息济蝉。當(dāng)操作成功完成時(shí)杰刽,Promise 對象的 then 方法就會被調(diào)用;否則就會觸發(fā) catch王滤。如:

const myFirstPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve("成功!");
    }, 250);
});

myFirstPromise.then((data) => {
    console.log("Yay! " + data);
}).catch((e) => {...});

51. 什么是 async/await 及其如何工作贺嫂?

async/await是 JS 中編寫異步或非阻塞代碼的新方法。它建立在Promises之上雁乡,相對于 Promise 和回調(diào)第喳,它的可讀性和簡潔度都更高。但是踱稍,在使用此功能之前曲饱,我們必須先學(xué)習(xí)Promises的基礎(chǔ)知識,因?yàn)檎缥抑八f珠月,它是基于Promise構(gòu)建的扩淀,這意味著幕后使用仍然是Promise。

使用 Promise

function callApi() {
  return fetch("url/to/api/endpoint")
    .then(resp => resp.json())
    .then(data => {
      //do something with "data"
    }).catch(err => {
      //do something with "err"
    });
}

使用async/await

在async/await中啤挎,我們使用 tru/catch 語法來捕獲異常驻谆。

async function callApi() {
  try {
    const resp = await fetch("url/to/api/endpoint");
    const data = await resp.json();
    //do something with "data"
  } catch (e) {
    //do something with "err"
  }
}

注意: 使用 async關(guān)鍵聲明函數(shù)會隱式返回一個(gè)Promise。

const giveMeOne = async () => 1;

giveMeOne()
  .then((num) => {
    console.log(num); // logs 1
  });

注意: await關(guān)鍵字只能在async function中使用侵浸。在任何非async function的函數(shù)中使用await關(guān)鍵字都會拋出錯(cuò)誤旺韭。await關(guān)鍵字在執(zhí)行下一行代碼之前等待右側(cè)表達(dá)式(可能是一個(gè)Promise)返回。

const giveMeOne = async () => 1;

function getOne() {
  try {
    const num = await giveMeOne();
    console.log(num);
  } catch (e) {
    console.log(e);
  }
}

// Uncaught SyntaxError: await is only valid in async function

async function getTwo() {
  try {
    const num1 = await giveMeOne(); // 這行會等待右側(cè)表達(dá)式執(zhí)行完成
    const num2 = await giveMeOne();
    return num1 + num2;
  } catch (e) {
    console.log(e);
  }
}

await getTwo(); // 2

52. 展開(spread )運(yùn)算符和 剩余(Rest) 運(yùn)算符有什么區(qū)別掏觉?

展開運(yùn)算符(spread)是三個(gè)點(diǎn)(...)区端,可以將一個(gè)數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。說的通俗易懂點(diǎn)澳腹,有點(diǎn)像化骨綿掌织盼,把一個(gè)大元素給打散成一個(gè)個(gè)單獨(dú)的小元素杨何。

剩余運(yùn)算符也是用三個(gè)點(diǎn)(...)表示,它的樣子看起來和展開操作符一樣沥邻,但是它是用于解構(gòu)數(shù)組和對象危虱。在某種程度上,剩余元素和展開元素相反唐全,展開元素會“展開”數(shù)組變成多個(gè)元素埃跷,剩余元素會收集多個(gè)元素和“壓縮”成一個(gè)單一的元素。

function add(a, b) {
  return a + b;
};

const nums = [5, 6];
const sum = add(...nums);
console.log(sum);

在本例中邮利,我們在調(diào)用add函數(shù)時(shí)使用了展開操作符弥雹,對nums數(shù)組進(jìn)行展開。所以參數(shù)a的值是5 延届,b的值是6剪勿,所以sum 是11。

function add(...rest) {
  return rest.reduce((total,current) => total + current);
};

console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15

在本例中方庭,我們有一個(gè)add函數(shù)厕吉,它接受任意數(shù)量的參數(shù),并將它們?nèi)肯嗉有的睿缓蠓祷乜倲?shù)头朱。

const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]

這里,我們使用剩余操作符提取所有剩余的數(shù)組值龄减,并將它們放入除第一項(xiàng)之外的其他數(shù)組中髓窜。

53. 什么是默認(rèn)參數(shù)?

默認(rèn)參數(shù)是在 JS 中定義默認(rèn)變量的一種新方法欺殿,它在ES6或ECMAScript 2015版本中可用。

//ES5 Version
function add(a,b){
  a = a || 0;
  b = b || 0;
  return a + b;
}

//ES6 Version
function add(a = 0, b = 0){
  return a + b;
}
add(1); // returns 1

我們還可以在默認(rèn)參數(shù)中使用解構(gòu)鳖敷。

function getFirst([first, ...rest] = [0, 1]) {
  return first;
}

getFirst();  // 0
getFirst([10,20,30]);  // 10

function getArr({ nums } = { nums: [1, 2, 3, 4] }){
    return nums;
}

getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]

我們還可以使用先定義的參數(shù)再定義它們之后的參數(shù)脖苏。

function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
  callback();
}
doSomethingWithValue(); //"Hello World"

54. 什么是包裝對象(wrapper object)?

我們現(xiàn)在復(fù)習(xí)一下JS的數(shù)據(jù)類型定踱,JS數(shù)據(jù)類型被分為兩大類棍潘,基本類型和引用類型。

基本類型:Undefined,Null,Boolean,Number,String,Symbol,BigInt

引用類型:Object,Array,Date,RegExp等崖媚,說白了就是對象亦歉。

其中引用類型有方法和屬性,但是基本類型是沒有的畅哑,但我們經(jīng)常會看到下面的代碼:

let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"

name類型是 string肴楷,屬于基本類型,所以它沒有屬性和方法荠呐,但是在這個(gè)例子中赛蔫,我們調(diào)用了一個(gè)toUpperCase()方法砂客,它不會拋出錯(cuò)誤,還返回了對象的變量值呵恢。

原因是基本類型的值被臨時(shí)轉(zhuǎn)換或強(qiáng)制轉(zhuǎn)換為對象鞠值,因此name變量的行為類似于對象。除null和undefined之外的每個(gè)基本類型都有自己包裝對象渗钉。也就是:String彤恶,Number,Boolean鳄橘,Symbol和BigInt声离。在這種情況下,name.toUpperCase()在幕后看起來如下:

console.log(new String(name).toUpperCase()); // "MARKO"

在完成訪問屬性或調(diào)用方法之后挥唠,新創(chuàng)建的對象將立即被丟棄抵恋。

55. 隱式和顯式轉(zhuǎn)換有什么區(qū)別)?

隱式強(qiáng)制轉(zhuǎn)換是一種將值轉(zhuǎn)換為另一種類型的方法宝磨,這個(gè)過程是自動完成的弧关,無需我們手動操作。

假設(shè)我們下面有一個(gè)例子唤锉。

console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12

第一個(gè)console.log語句結(jié)果為16世囊。在其他語言中,這會拋出編譯時(shí)錯(cuò)誤窿祥,但在 JS 中株憾,1被轉(zhuǎn)換成字符串,然后與+運(yùn)算符連接晒衩。我們沒有做任何事情嗤瞎,它是由 JS 自動完成。

第二個(gè)console.log語句結(jié)果為1听系,JS 將false轉(zhuǎn)換為boolean 值為 0贝奇,,true為1,因此結(jié)果為1靠胜。

第三個(gè)console.log語句結(jié)果12掉瞳,它將'2'轉(zhuǎn)換為一個(gè)數(shù)字,然后乘以6 * 2浪漠,結(jié)果是12陕习。

而顯式強(qiáng)制是將值轉(zhuǎn)換為另一種類型的方法,我們需要手動轉(zhuǎn)換址愿。

console.log(1 + parseInt('6'));

在本例中该镣,我們使用parseInt函數(shù)將'6'轉(zhuǎn)換為number ,然后使用+運(yùn)算符將1和6相加响谓。

56. 什么是NaN拌牲?以及如何檢查值是否為NaN俱饿?

NaN表示“非數(shù)字”是 JS 中的一個(gè)值,該值是將數(shù)字轉(zhuǎn)換或執(zhí)行為非數(shù)字值的運(yùn)算結(jié)果塌忽,因此結(jié)果為NaN拍埠。

let a;

console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN

JS 有一個(gè)內(nèi)置的isNaN方法,用于測試值是否為isNaN值土居,但是這個(gè)函數(shù)有一個(gè)奇怪的行為枣购。

console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true

所有這些console.log語句都返回true,即使我們傳遞的值不是NaN擦耀。

在ES6中棉圈,建議使用Number.isNaN方法,因?yàn)樗_實(shí)會檢查該值(如果確實(shí)是NaN)眷蜓,或者我們可以使自己的輔助函數(shù)檢查此問題分瘾,因?yàn)樵?JS 中,NaN是唯一的值吁系,它不等于自己德召。

function checkIfNaN(value) {
  return value !== value;
}

57. 如何判斷值是否為數(shù)組?

我們可以使用Array.isArray方法來檢查值是否為數(shù)組汽纤。當(dāng)傳遞給它的參數(shù)是數(shù)組時(shí)上岗,它返回true,否則返回false蕴坪。

console.log(Array.isArray(5));  // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false

console.log(Array.isArray([])); // true

如果環(huán)境不支持此方法肴掷,則可以使用polyfill實(shí)現(xiàn)。

function isArray(value){
 return Object.prototype.toString.call(value) === "[object Array]"
}

當(dāng)然還可以使用傳統(tǒng)的方法:

let a = []
if (a instanceof Array) {
  console.log('是數(shù)組')
} else {
  console.log('非數(shù)組')
}

58. 如何在不使用%模運(yùn)算符的情況下檢查一個(gè)數(shù)字是否是偶數(shù)背传?

我們可以對這個(gè)問題使用按位&運(yùn)算符呆瞻,&對其操作數(shù)進(jìn)行運(yùn)算,并將其視為二進(jìn)制值径玖,然后執(zhí)行與運(yùn)算栋烤。

function isEven(num) {
  if (num & 1) {
    return false
  } else {
    return true
  }
}

0 二進(jìn)制數(shù)是 000
1 二進(jìn)制數(shù)是 001
2 二進(jìn)制數(shù)是 010
3 二進(jìn)制數(shù)是 011
4 二進(jìn)制數(shù)是 100
5 二進(jìn)制數(shù)是 101
6 二進(jìn)制數(shù)是 110
7 二進(jìn)制數(shù)是 111

以此類推...

與運(yùn)算的規(guī)則如下:

a b a & b
0 0 0
0 1 0
1 1 1

因此,當(dāng)我們執(zhí)行console.log(5&1)這個(gè)表達(dá)式時(shí)挺狰,結(jié)果為1。首先买窟,&運(yùn)算符將兩個(gè)數(shù)字都轉(zhuǎn)換為二進(jìn)制丰泊,因此5變?yōu)?01,1變?yōu)?01始绍。

然后瞳购,它使用按位懷運(yùn)算符比較每個(gè)位(0和1)。 101&001亏推,從表中可以看出学赛,如果a & b為1年堆,所以5&1結(jié)果為1。

101 & 001
101
001
001

首先我們比較最左邊的1&0盏浇,結(jié)果是0变丧。

然后我們比較中間的0&0,結(jié)果是0绢掰。

然后我們比較最后1&1痒蓬,結(jié)果是1。

最后滴劲,得到一個(gè)二進(jìn)制數(shù)001攻晒,對應(yīng)的十進(jìn)制數(shù),即1班挖。

由此我們也可以算出console.log(4 & 1) 結(jié)果為0鲁捏。知道4的最后一位是0,而0 & 1 將是0萧芙。如果你很難理解這一點(diǎn)给梅,我們可以使用遞歸函數(shù)來解決此問題。

function isEven(num) {

    if (num < 0 || num === 1) return false;

    if (num == 0) return true;
    return isEven(num - 2);

}

59. 如何檢查對象中是否存在某個(gè)屬性末购?

檢查對象中是否存在屬性有三種方法破喻。

第一種使用 in 操作符號:

const o = {
  "prop" : "bwahahah",
  "prop2" : "hweasa"
};

console.log("prop" in o); // true
console.log("prop1" in o); // false

第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會返回一個(gè)布爾值盟榴,指示對象自身屬性中是否具有指定的屬性(也就是曹质,是否有指定的鍵)。

console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false

第三種使用括號符號obj["prop"]擎场。如果屬性存在羽德,它將返回該屬性的值,否則將返回undefined迅办。

console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined

60. AJAX 是什么宅静?

即異步的 JavaScript 和 XML,是一種用于創(chuàng)建快速動態(tài)網(wǎng)頁的技術(shù)站欺,傳統(tǒng)的網(wǎng)頁(不使用 AJAX)如果需要更新內(nèi)容姨夹,必需重載整個(gè)網(wǎng)頁面。使用AJAX則不需要加載更新整個(gè)網(wǎng)頁矾策,實(shí)現(xiàn)部分內(nèi)容更新

用到AJAX的技術(shù):

  • HTML - 網(wǎng)頁結(jié)構(gòu)
  • CSS - 網(wǎng)頁的樣式
  • JavaScript - 操作網(wǎng)頁的行為和更新DOM
  • XMLHttpRequest API - 用于從服務(wù)器發(fā)送和獲取數(shù)據(jù)
  • PHP磷账,Python,Nodejs - 某些服務(wù)器端語言

61. 如何在 JS 中創(chuàng)建對象贾虽?

使用對象字面量:

  const o = {
   name: "Mark",
   greeting() {
      return `Hi, I'm ${this.name}`;
   }
  };

  o.greeting(); //returns "Hi, I'm Mark"

使用構(gòu)造函數(shù):

function Person(name) {
   this.name = name;
}

Person.prototype.greeting = function () {
   return `Hi, I'm ${this.name}`;
}

const mark = new Person("Mark");

mark.greeting(); //returns "Hi, I'm Mark"

使用 Object.create 方法:

const n = {
   greeting() {
      return `Hi, I'm ${this.name}`;
   }
};

const o = Object.create(n); // sets the prototype of "o" to be "n"

o.name = "Mark";

console.log(o.greeting()); // logs "Hi, I'm Mark"

62. Object.seal 和 Object.freeze 方法之間有什么區(qū)別逃糟?

這兩種方法之間的區(qū)別在于,當(dāng)我們對一個(gè)對象使用Object.freeze方法時(shí),該對象的屬性是不可變的绰咽,這意味著我們不能更改或編輯這些屬性的值菇肃。而在Obj.Engor方法中,我們可以改變現(xiàn)有的屬性取募。

Object.freeze()

Object.freeze() 方法可以凍結(jié)一個(gè)對象琐谤。一個(gè)被凍結(jié)的對象再也不能被修改;凍結(jié)了一個(gè)對象則不能向這個(gè)對象添加新的屬性矛辕,不能刪除已有屬性笑跛,不能修改該對象已有屬性的可枚舉性、可配置性聊品、可寫性飞蹂,以及不能修改已有屬性的值。此外翻屈,凍結(jié)一個(gè)對象后該對象的原型也不能被修改陈哑。freeze() 返回和傳入的參數(shù)相同的對象。

Object.seal()

Object.seal()方法封閉一個(gè)對象伸眶,阻止添加新屬性并將所有現(xiàn)有屬性標(biāo)記為不可配置惊窖。當(dāng)前屬性的值只要可寫就可以改變。

方法的相同點(diǎn):

  • ES5新增厘贼。

  • 對象不可能擴(kuò)展界酒,也就是不能再添加新的屬性或者方法。

  • 對象已有屬性不允許被刪除嘴秸。

  • 對象屬性特性不可以重新配置毁欣。

方法不同點(diǎn):

  • Object.seal方法生成的密封對象,如果屬性是可寫的岳掐,那么可以修改屬性值凭疮。

  • Object.freeze方法生成的凍結(jié)對象,屬性都是不可寫的串述,也就是屬性值無法更改执解。

63. in 運(yùn)算符和 Object.hasOwnProperty 方法有什么區(qū)別?

如你所知纲酗,這兩個(gè)特性都檢查對象中是否存在屬性衰腌,它將返回true或false。它們之間的區(qū)別在于觅赊,in操作符還會檢查對象的原型鏈右蕊,如果屬性在當(dāng)前對象中沒有找到,而hasOwnProperty方法只檢查屬性是否存在于當(dāng)前對象中茉兰,而忽略原型鏈。

hasOwnPropert方法

hasOwnPropert()方法返回值是一個(gè)布爾值欣簇,指示對象自身屬性中是否具有指定的屬性规脸,因此這個(gè)方法會忽略掉那些從原型鏈上繼承到的屬性坯约。

看下面的例子:

Object.prototype.phone= '15345025546';

let obj = {
    name: '西門大官人',
    age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true

可以看到,如果在函數(shù)原型上定義一個(gè)變量phone莫鸭,hasOwnProperty方法會直接忽略掉闹丐。

in 運(yùn)算符

如果指定的屬性在指定的對象或其原型鏈中,則in 運(yùn)算符返回true被因。

還是用上面的例子來演示:

console.log('phone' in obj) // true

可以看到in運(yùn)算符會檢查它或者其原型鏈?zhǔn)欠癜哂兄付Q的屬性卿拴。

64. 有哪些方法可以處理 JS 中的異步代碼?

  • 回調(diào)
  • Promise
  • async/await

還有一些庫:async.js, bluebird, q, co

65. 函數(shù)表達(dá)式和函數(shù)聲明之間有什么區(qū)別?

看下面的例子:

hoistedFunc();
notHoistedFunc();

function hoistedFunc(){
  console.log("注意:我會被提升");
}

var notHoistedFunc = function(){
  console.log("注意:我沒有被提升");
}

notHoistedFunc調(diào)用拋出異常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc調(diào)用不會给郊,因?yàn)閔oistedFunc會被提升到作用域的頂部他托,而notHoistedFunc 不會。

66. 調(diào)用函數(shù)北滥,可以使用哪些方法?

在 JS 中有4種方法可以調(diào)用函數(shù)。

作為函數(shù)調(diào)用——如果一個(gè)函數(shù)沒有作為方法壕曼、構(gòu)造函數(shù)、apply等浊、call 調(diào)用時(shí)腮郊,此時(shí) this 指向的是 window 對象(非嚴(yán)格模式)

  //Global Scope

  function add(a,b){
    console.log(this);
    return a + b;
  }

  add(1,5); // 打印 "window" 對象和 6

  const o = {
    method(callback){
      callback();
    }
  }

  o.method(function (){
      console.log(this); // 打印 "window" 對象
  });

作為方法調(diào)用——如果一個(gè)對象的屬性有一個(gè)函數(shù)的值,我們就稱它為方法筹燕。調(diào)用該方法時(shí)轧飞,該方法的this值指向該對象。

const details = {
  name : "Marko",
  getName(){
    return this.name;
  }
}

details.getName(); // Marko
// the "this" value inside "getName" method will be the "details" object

作為構(gòu)造函數(shù)的調(diào)用-如果在函數(shù)之前使用new關(guān)鍵字調(diào)用了函數(shù)庄萎,則該函數(shù)稱為構(gòu)造函數(shù)踪少。構(gòu)造函數(shù)里面會默認(rèn)創(chuàng)建一個(gè)空對象,并將this指向該對象糠涛。

function Employee(name, position, yearHired) {
  // creates an empty object {}
  // then assigns the empty object to the "this" keyword
  // this = {};
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
  // inherits from Employee.prototype
  // returns the "this" value implicitly if no
  // explicit return statement is specified
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

使用apply和call方法調(diào)用——如果我們想顯式地指定一個(gè)函數(shù)的this值援奢,我們可以使用這些方法,這些方法對所有函數(shù)都可用忍捡。

const obj1 = {
 result:0
};

const obj2 = {
 result:0
};


function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}


reduceAdd.apply(obj1, [1, 2, 3, 4, 5]);  // reduceAdd 函數(shù)中的 this 對象將是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函數(shù)中的 this 對象將是 obj2

67. 什么是緩存及它有什么作用集漾?

緩存是建立一個(gè)函數(shù)的過程,這個(gè)函數(shù)能夠記住之前計(jì)算的結(jié)果或值砸脊。使用緩存函數(shù)是為了避免在最后一次使用相同參數(shù)的計(jì)算中已經(jīng)執(zhí)行的函數(shù)的計(jì)算具篇。這節(jié)省了時(shí)間,但也有不利的一面凌埂,即我們將消耗更多的內(nèi)存來保存以前的結(jié)果驱显。

68. 手動實(shí)現(xiàn)緩存方法

function memoize(fn) {
  const cache = {};
  return function (param) {
    if (cache[param]) {
      console.log('cached');
      return cache[param];
    } else {
      let result = fn(param);
      cache[param] = result;
      console.log(`not cached`);
      return result;
    }
  }
}

const toUpper = (str ="")=> str.toUpperCase();

const toUpperMemoized = memoize(toUpper);

toUpperMemoized("abcdef");
toUpperMemoized("abcdef");

這個(gè)緩存函數(shù)適用于接受一個(gè)參數(shù)。我們需要改變下,讓它接受多個(gè)參數(shù)埃疫。

const slice = Array.prototype.slice;
function memoize(fn) {
  const cache = {};
  return (...args) => {
    const params = slice.call(args);
    console.log(params);
    if (cache[params]) {
      console.log('cached');
      return cache[params];
    } else {
      let result = fn(...args);
      cache[params] = result;
      console.log(`not cached`);
      return result;
    }
  }
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) =>
 numbers.reduce((total, cur) => total + cur, startingValue);

const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);

memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");

memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);

69. 為什么typeof null 返回 object伏恐?如何檢查一個(gè)值是否為 null?

typeof null == 'object'總是返回true栓霜,因?yàn)檫@是自 JS 誕生以來null的實(shí)現(xiàn)翠桦。曾經(jīng)有人提出將typeof null == 'object'修改為typeof null == 'null',但是被拒絕了胳蛮,因?yàn)檫@將導(dǎo)致更多的bug销凑。

我們可以使用嚴(yán)格相等運(yùn)算符===來檢查值是否為null。

function isNull(value){
  return value === null;
}

70. new 關(guān)鍵字有什么作用仅炊?

new關(guān)鍵字與構(gòu)造函數(shù)一起使用以創(chuàng)建對象在JavaScript中斗幼。

下面看看例子:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);
  1. new關(guān)鍵字做了4件事:
  2. 創(chuàng)建空對象 {}
  3. 將空對象分配給 this 值
  4. 將空對象的proto指向構(gòu)造函數(shù)的prototype
  5. 如果沒有使用顯式return語句,則返回this

根據(jù)上面描述的茂洒,它將首先創(chuàng)建一個(gè)空對象{}孟岛,然后它將this值賦給這個(gè)空對象this={},并向這個(gè)對象添加屬性督勺。因?yàn)槲覀儧]有顯式的return語句渠羞,所以它會自動為我們返回this。

原文鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末智哀,一起剝皮案震驚了整個(gè)濱河市次询,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓷叫,老刑警劉巖屯吊,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異摹菠,居然都是意外死亡盒卸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門次氨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔽介,“玉大人,你說我怎么就攤上這事煮寡『缧睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵幸撕,是天一觀的道長薇组。 經(jīng)常有香客問我,道長坐儿,這世上最難降的妖魔是什么律胀? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任宋光,我火速辦了婚禮,結(jié)果婚禮上炭菌,老公的妹妹穿的比我還像新娘跃须。我一直安慰自己,他們只是感情好娃兽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尽楔,像睡著了一般投储。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阔馋,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天玛荞,我揣著相機(jī)與錄音,去河邊找鬼呕寝。 笑死勋眯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的下梢。 我是一名探鬼主播客蹋,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孽江!你這毒婦竟也來了讶坯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤岗屏,失蹤者是張志新(化名)和其女友劉穎辆琅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體这刷,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婉烟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暇屋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片似袁。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖率碾,靈堂內(nèi)的尸體忽然破棺而出叔营,到底是詐尸還是另有隱情,我是刑警寧澤所宰,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布绒尊,位于F島的核電站,受9級特大地震影響仔粥,放射性物質(zhì)發(fā)生泄漏婴谱。R本人自食惡果不足惜蟹但,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谭羔。 院中可真熱鬧华糖,春花似錦、人聲如沸瘟裸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽话告。三九已至兼搏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沙郭,已是汗流浹背佛呻。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留病线,地道東北人吓著。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像送挑,于是被迫代替她去往敵國和親绑莺。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容