js考點(diǎn)

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ù)沒有指定返回值,會(huì)默認(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會(huì)返回undefined,null會(huì)返回object。

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

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

&& 也叫邏輯與剧劝,在表達(dá)式中從左往右查找橄登,找到一個(gè)虛值表達(dá)式并返回它,如果沒有找到任何虛值表達(dá)式担平,則會(huì)返回最后一個(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ù)字,它不會(huì)執(zhí)行任何操作闻蛀。

5. DOM 是什么匪傍?

DOM 代表文檔對(duì)象模型,是 HTML 和 XML 文檔的接口(API)觉痛。當(dāng)瀏覽器第一次讀取(解析)HTML文檔時(shí)役衡,它會(huì)創(chuàng)建一個(gè)大對(duì)象,一個(gè)基于 HTM L文檔的非常大的對(duì)象薪棒,這就是DOM手蝎。它是一個(gè)從 HTML 文檔中建模的樹狀結(jié)構(gòu)。DOM 用于交互和修改DOM結(jié)構(gòu)或特定元素或節(jié)點(diǎn)俐芯。

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

6. 什么是事件傳播?

當(dāng)事件發(fā)生在DOM元素上時(shí)欠动,該事件并不完全發(fā)生在那個(gè)元素上骇吭。在“冒泡階段”中吨述,事件冒泡或向上傳播至父級(jí)岩睁,祖父母,祖父母或父級(jí)锐极,直到到達(dá)window為止笙僚;而在“捕獲階段”中芳肌,事件從window開始向下觸發(fā)元素 事件或event.target灵再。

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

捕獲階段–事件從 window 開始,然后向下到每個(gè)元素亿笤,直到到達(dá)目標(biāo)元素翎迁。
目標(biāo)階段–事件已達(dá)到目標(biāo)元素。
冒泡階段–事件從目標(biāo)元素冒泡净薛,然后上升到每個(gè)元素汪榔,直到到達(dá) window。

7. 什么是事件冒泡肃拜?

當(dāng)事件發(fā)生在DOM元素上時(shí)痴腌,該事件并不完全發(fā)生在那個(gè)元素上。在冒泡階段燃领,事件冒泡士聪,或者事件會(huì)向上傳播,發(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元素种玛,它將分別在控制臺(tái)上記錄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元素,它將分別在控制臺(tái)上打印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()方法?

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

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

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

顯然,由于我們嘗試訪問someprop屬性中的x屬性逗余,而 someprop 并沒有在對(duì)象中特咆,所以值為 undefined。記住對(duì)象本身不存在的屬性录粱,并且其原型的默認(rèn)值為undefined腻格。因?yàn)閡ndefined沒有屬性x,所以試圖訪問將會(huì)報(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是我們?cè)谄渖巷@式附加事件處理程序的元素举瑰。

<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蔬螟,它也會(huì)打印最外面的div標(biāo)簽此迅。在此示例中,我們可以得出結(jié)論旧巾,event.currentTarget是附加事件處理程序的元素耸序。

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

==用于一般比較菠齿,===用于嚴(yán)格比較佑吝,==在比較的時(shí)候可以轉(zhuǎn)換數(shù)據(jù)類型,===嚴(yán)格比較绳匀,只要類型不匹配就返回flase。

先來看看 == :

強(qiáng)制是將值轉(zhuǎn)換為另一種類型的過程炸客。在這種情況下疾棵,==會(huì)執(zhí)行隱式強(qiáng)制。在比較兩個(gè)值之前痹仙,==需要執(zhí)行一些規(guī)則是尔。

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

如果x和y的類型相同开仰,則 JS 會(huì)換成===操作符進(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首先在對(duì)象中使用valueOf方法,然后使用toString方法來獲取該對(duì)象的原始值战得。

舉個(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()方法將對(duì)象轉(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è)相似的對(duì)象時(shí)返回 false庸娱?

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

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

JS 以不同的方式比較對(duì)象和基本類型谐算。在基本類型中熟尉,JS 通過值對(duì)它們進(jìn)行比較,而在對(duì)象中氯夷,JS 通過引用或存儲(chǔ)變量的內(nèi)存中的地址對(duì)它們進(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á)式的值馅闽?

可以使用逗號(hào)運(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ù)移動(dòng)到其(全局或函數(shù))作用域頂部的術(shù)語呕诉。

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

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

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

注意:只有使用var聲明的變量法精,或者函數(shù)聲明才會(huì)被提升,相反痴突,函數(shù)表達(dá)式或箭頭函數(shù)搂蜓,let和const聲明的變量,這些都不會(huì)被提升辽装。

假設(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"));
*/

編譯階段完成后丰涉,它將啟動(dòng)執(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í),它會(huì)在作用域鏈中查找outerVar的值姑宽,此時(shí)的outerVar的值將為 "outer"遣耍。

現(xiàn)在,當(dāng)我們調(diào)用引用了innerFunc的x變量時(shí)炮车,innerParam將具有一個(gè)inner值舵变,因?yàn)檫@是我們?cè)谡{(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)我們?cè)谘h(huán)后在該數(shù)組中調(diào)用其中一個(gè)函數(shù)時(shí)住涉,它會(huì)打印5麸锉,因?yàn)槲覀兊玫絠的當(dāng)前值為5,我們可以訪問它舆声,因?yàn)樗侨肿兞俊?/p>

因?yàn)殚]包在創(chuàng)建變量時(shí)會(huì)保留該變量的引用而不是其值花沉。我們可以使用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)格模式幫助我們?cè)诖a的早期避免 bug,并為其添加限制架曹。

嚴(yán)格模式的一些限制:
  • 變量必須聲明后再使用

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

  • 不能使用with語句

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

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

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

  • 不能刪除變量delete prop躲株,會(huì)報(bào)錯(cuò)钙勃,只能刪除屬性delete global[prop]

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

  • eval和arguments不能被重新賦值

  • arguments不會(huì)自動(dòng)反映函數(shù)參數(shù)的變化

  • 不能使用arguments.callee

  • 不能使用arguments.caller

  • 禁止this指向全局對(duì)象

  • 不能使用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ù)的對(duì)象的值炼彪。this值的變化取決于我們使用它的上下文和我們?cè)谀睦锸褂盟?/p>

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對(duì)象,該對(duì)象當(dāng)前是執(zhí)行函數(shù)的“所有者”對(duì)象朋腋。

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

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

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

上面打印Ford Ranger齐疙,這很奇怪,因?yàn)樵诘谝粋€(gè)console.log語句中打印的是Ford Mustang旭咽。這樣做的原因是getCarName方法有一個(gè)不同的“所有者”對(duì)象贞奋,即window對(duì)象。在全局作用域中使用var關(guān)鍵字聲明變量會(huì)在window對(duì)象中附加與變量名稱相同的屬性穷绵。請(qǐng)記住轿塔,當(dāng)沒有使用“use strict”時(shí),在全局作用域中this指的是window對(duì)象仲墨。

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

本例中的this和window引用同一個(gè)對(duì)象勾缭。

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

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

apply和call方法期望第一個(gè)參數(shù)是一個(gè)對(duì)象目养,該對(duì)象是函數(shù)內(nèi)部this的值俩由。

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

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

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

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

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

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

如果我們要獲取myFavoriteObj對(duì)象中的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對(duì)象恩沛。

25. 對(duì)象的 prototype(原型) 是什么在扰?

簡單地說,原型就是對(duì)象的藍(lán)圖复唤。如果它存在當(dāng)前對(duì)象中健田,則將其用作屬性和方法的回退。它是在對(duì)象之間共享屬性和功能的方法佛纫,這也是JavaScript實(shí)現(xiàn)繼承的核心妓局。

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

即使o對(duì)象中不存在o.toString方法,它也不會(huì)引發(fā)錯(cuò)誤呈宇,而是返回字符串[object Object]好爬。當(dāng)對(duì)象中不存在屬性時(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 (){}包裹在在括號(hào)()內(nèi),然后再用另一個(gè)括號(hào)()調(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的鏈接,它提供了一些我們?cè)诖a中使用的全局函數(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的對(duì)象。

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í),打印對(duì)應(yīng)的下標(biāo)值聊疲。但在此外上述代碼不起作用茬底,這里每次點(diǎn)擊 li 打印 i 的值都是5,這是由于閉包的原因获洲。

閉包只是函數(shù)記住其當(dāng)前作用域阱表,父函數(shù)作用域和全局作用域的變量引用的能力。當(dāng)我們?cè)谌肿饔糜騼?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會(huì)為每次迭代創(chuàng)建一個(gè)新的作用域蒜鸡,我們捕獲i的值并將其傳遞給currentIndex參數(shù),因此調(diào)用IIFE時(shí)牢裳,每次迭代的currentIndex值都是不同的逢防。

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

apply() 方法調(diào)用一個(gè)具有給定this值的函數(shù)蒲讯,以及作為一個(gè)數(shù)組(或類似數(shù)組對(duì)象)提供的參數(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ù)組對(duì)象)的前提下調(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ù)流動(dòng)的忙芒。與面向?qū)ο缶幊绦纬蓪?duì)比示弓,面向?qū)ο笾袘?yīng)用程序的狀態(tài)通常與對(duì)象中的方法共享和共處。

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

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

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ù)也稱之為第一級(jí)函數(shù)(First-class Function)蘑斧。不僅如此糟红,JavaScript中的函數(shù)還充當(dāng)了類的構(gòu)造函數(shù)的作用艾帐,同時(shí)又是一個(gè)Function類的實(shí)例(instance)。這樣的多重身份讓JavaScript的函數(shù)變得非常重要盆偿。

34. 手動(dòng)實(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í)乐横,我們都會(huì)創(chuàng)建一個(gè) result 數(shù)組
// 因?yàn)槲覀儾幌敫淖冊(cè)紨?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. 手動(dòng)實(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í),我們都會(huì)創(chuàng)建一個(gè) result 數(shù)組
 // 因?yàn)槲覀儾幌敫淖冊(cè)紨?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. 手動(dòng)實(shí)現(xiàn)Array.prototype.reduce方法

reduce() 方法對(duì)數(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 的對(duì)象是什么搀矫?

arguments對(duì)象是函數(shù)中傳遞的參數(shù)值的集合。它是一個(gè)類似數(shù)組的對(duì)象刻肄,因?yàn)樗幸粋€(gè)length屬性瓤球,我們可以使用數(shù)組索引表示法arguments[1]來訪問單個(gè)值,但它沒有數(shù)組中的內(nèi)置方法肄方,如:forEach冰垄、reduce、filter和map权她。

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

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

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

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í)隅要,它會(huì)拋出一個(gè)ReferenceError: arguments is not defined error蝴罪。使用rest語法,可以解決這個(gè)問題步清。

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

這會(huì)自動(dòng)將所有參數(shù)值放入數(shù)組中要门。

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

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

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會(huì)變成一個(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ò)了轧粟!能我就是對(duì)的!

突然感覺JavaScript好沒有尊嚴(yán)脓魏,為啥要搞個(gè)人出來約束自己兰吟,

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

41. ES6或ECMAScript 2015有哪些新特性混蔼?

箭頭函數(shù)

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

42. var,let和const的區(qū)別是什么?

var聲明的變量會(huì)掛載在window上珊燎,而let和const聲明的變量不會(huì):

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;

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

暫存死區(qū)

var a = 100;

if(1){
  a = 10;
//在當(dāng)前塊作用域中存在a使用let/const聲明的情況下延旧,給a賦值10時(shí),只會(huì)在當(dāng)前作用域找變量a槽地,
// 而這時(shí)垄潮,還未到聲明時(shí)候,所以控制臺(tái)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ù)版本中纱烘,我們只需要()括號(hào),不需要 return 語句祈餐,因?yàn)槿绻覀冎挥幸粋€(gè)表達(dá)式或值需要返回擂啥,箭頭函數(shù)就會(huì)有一個(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ù)帆阳。如果我們?cè)谝粋€(gè)箭頭函數(shù)中有一個(gè)參數(shù)哺壶,則可以省略括號(hào)。

const getArgs = () => arguments

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

箭頭函數(shù)不能訪問arguments對(duì)象蜒谤。所以調(diào)用第一個(gè)getArgs函數(shù)會(huì)拋出一個(gè)錯(cuò)誤山宾。相反,我們可以使用rest參數(shù)來獲得在箭頭函數(shù)中傳遞的所有參數(shù)芭逝。

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

箭頭函數(shù)沒有自己的this值塌碌。它捕獲詞法作用域函數(shù)的this值,在此示例中旬盯,addAll函數(shù)將復(fù)制computeResult 方法中的this值台妆,如果我們?cè)谌肿饔糜蚵暶骷^函數(shù)翎猛,則this值為 window 對(duì)象。

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)建字符串的一種新方法遗座。我們可以通過使用反引號(hào)使模板字符串化。

//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. 什么是對(duì)象解構(gòu)?

對(duì)象析構(gòu)是從對(duì)象或數(shù)組中獲取或提取值的一種新的逮光、更簡潔的方法代箭。假設(shè)有如下的對(duì)象:

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

從對(duì)象獲取屬性,早期方法是創(chuàng)建一個(gè)與對(duì)象屬性同名的變量涕刚。這種方法很麻煩嗡综,因?yàn)槲覀円獮槊總€(gè)屬性創(chuàng)建一個(gè)新變量。假設(shè)我們有一個(gè)大對(duì)象杜漠,它有很多屬性和方法极景,用這種方法提取屬性會(huì)很麻煩。

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對(duì)象阁吝,它是如何工作的?

Set 對(duì)象允許你存儲(chǔ)任何類型的唯一值械拍,無論是原始值或者是對(duì)象引用突勇。

我們可以使用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對(duì)象甲馋,所以我們可以以鏈?zhǔn)降姆绞皆俅问褂胊dd。如果一個(gè)值已經(jīng)存在于Set對(duì)象中迄损,那么它將不再被添加摔刁。

set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// 后一個(gè)“k”不會(huì)被添加到set對(duì)象中,因?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對(duì)象來刪除數(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ù)也是對(duì)象的一種壤玫,同樣對(duì)象可以作為參數(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ù)〕煤模回調(diào)的一個(gè)很好的類比是沉唠,當(dāng)你打電話給某人,如果他們不接苛败,你留下一條消息满葛,你期待他們回調(diào)径簿。調(diào)用某人或留下消息的行為是事件或數(shù)據(jù),回調(diào)是你希望稍后發(fā)生的操作纱扭。

50. Promise 是什么牍帚?

Promise 是異步編程的一種解決方案:從語法上講,promise是一個(gè)對(duì)象乳蛾,從它可以獲取異步操作的消息暗赶;從本意上講,它是承諾肃叶,承諾它過一段時(shí)間會(huì)給你一個(gè)結(jié)果蹂随。promise有三種狀態(tài):pending(等待態(tài)),fulfiled(成功態(tài))因惭,rejected(失敗態(tài))岳锁;狀態(tài)一旦改變,就不會(huì)再變蹦魔。創(chuàng)造promise實(shí)例后激率,它會(huì)立即執(zhí)行。

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

如果我們?cè)诨卣{(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
})
})
})

如果我們?cè)谶@段代碼中使用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 對(duì)象會(huì)觸發(fā) fulfilled/rejected 狀態(tài),在其狀態(tài)處理方法中可以傳入?yún)?shù)/失敗信息玩般。當(dāng)操作成功完成時(shí)银觅,Promise 對(duì)象的 then 方法就會(huì)被調(diào)用;否則就會(huì)觸發(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之上久脯,相對(duì)于 Promise 和回調(diào)纳胧,它的可讀性和簡潔度都更高。但是帘撰,在使用此功能之前跑慕,我們必須先學(xué)習(xí)Promises的基礎(chǔ)知識(shí),因?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ù)會(huì)隱式返回一個(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)鍵字都會(huì)拋出錯(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(); // 這行會(huì)等待右側(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)為用逗號(hào)分隔的參數(shù)序列抒抬。說的通俗易懂點(diǎn),有點(diǎn)像化骨綿掌晤柄,把一個(gè)大元素給打散成一個(gè)個(gè)單獨(dú)的小元素擦剑。

剩余運(yùn)算符也是用三個(gè)點(diǎn)(...)表示,它的樣子看起來和展開操作符一樣芥颈,但是它是用于解構(gòu)數(shù)組和對(duì)象惠勒。在某種程度上,剩余元素和展開元素相反爬坑,展開元素會(huì)“展開”數(shù)組變成多個(gè)元素捉撮,剩余元素會(huì)收集多個(gè)元素和“壓縮”成一個(gè)單一的元素。

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

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

在本例中妇垢,我們?cè)谡{(diào)用add函數(shù)時(shí)使用了展開操作符,對(duì)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. 什么是包裝對(duì)象(wrapper object)醉蚁?

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

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

引用類型:Object,Array,Date,RegExp等,說白了就是對(duì)象妇智。

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

let name = "marko";

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

name類型是 string俘陷,屬于基本類型罗捎,所以它沒有屬性和方法,但是在這個(gè)例子中拉盾,我們調(diào)用了一個(gè)toUpperCase()方法桨菜,它不會(huì)拋出錯(cuò)誤,還返回了對(duì)象的變量值捉偏。

原因是基本類型的值被臨時(shí)轉(zhuǎn)換或強(qiáng)制轉(zhuǎn)換為對(duì)象倒得,因此name變量的行為類似于對(duì)象。除null和undefined之外的每個(gè)基本類型都有自己包裝對(duì)象夭禽。也就是:String霞掺,Number,Boolean讹躯,Symbol和BigInt菩彬。在這種情況下,name.toUpperCase()在幕后看起來如下:

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

在完成訪問屬性或調(diào)用方法之后潮梯,新創(chuàng)建的對(duì)象將立即被丟棄骗灶。

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

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

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

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

第一個(gè)console.log語句結(jié)果為16免都。在其他語言中,這會(huì)拋出編譯時(shí)錯(cuò)誤帆竹,但在 JS 中绕娘,1被轉(zhuǎn)換成字符串,然后與+運(yùn)算符連接栽连。我們沒有做任何事情业舍,它是由 JS 自動(dòng)完成。

第二個(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)換為另一種類型的方法耗啦,我們需要手動(dòng)轉(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í)會(huì)檢查該值(如果確實(shí)是NaN)雌芽,或者我們可以使自己的輔助函數(shù)檢查此問題授艰,因?yàn)樵?JS 中,NaN是唯一的值膘怕,它不等于自己。

function checkIfNaN(value) {
  return value !== value;
}
  1. 如何判斷值是否為數(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ù)髓堪?

我們可以對(duì)這個(gè)問題使用按位&運(yùn)算符,&對(duì)其操作數(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碑定,對(duì)應(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. 如何檢查對(duì)象中是否存在某個(gè)屬性外构?

檢查對(duì)象中是否存在屬性有三種方法普泡。

  • 第一種使用 in 操作符號(hào):

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

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

  • 第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會(huì)返回一個(gè)布爾值审编,指示對(duì)象自身屬性中是否具有指定的屬性(也就是撼班,是否有指定的鍵)。

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

  • 第三種使用括號(hào)符號(hào)obj["prop"]垒酬。如果屬性存在砰嘁,它將返回該屬性的值件炉,否則將返回undefined。

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

60. AJAX 是什么矮湘?

即異步的 JavaScript 和 XML斟冕,是一種用于創(chuàng)建快速動(dò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)建對(duì)象?

使用對(duì)象字面量:

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)我們對(duì)一個(gè)對(duì)象使用Object.freeze方法時(shí),該對(duì)象的屬性是不可變的炸枣,這意味著我們不能更改或編輯這些屬性的值虏等。而在Obj.Engor方法中,我們可以改變現(xiàn)有的屬性适肠。

Object.freeze()

Object.freeze() 方法可以凍結(jié)一個(gè)對(duì)象霍衫。一個(gè)被凍結(jié)的對(duì)象再也不能被修改;凍結(jié)了一個(gè)對(duì)象則不能向這個(gè)對(duì)象添加新的屬性侯养,不能刪除已有屬性敦跌,不能修改該對(duì)象已有屬性的可枚舉性、可配置性逛揩、可寫性柠傍,以及不能修改已有屬性的值。此外辩稽,凍結(jié)一個(gè)對(duì)象后該對(duì)象的原型也不能被修改惧笛。freeze() 返回和傳入的參數(shù)相同的對(duì)象。

Object.seal()

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

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

ES5新增喷众。

對(duì)象不可能擴(kuò)展各谚,也就是不能再添加新的屬性或者方法。

對(duì)象已有屬性不允許被刪除侮腹。

對(duì)象屬性特性不可以重新配置嘲碧。

方法不同點(diǎn):

Object.seal方法生成的密封對(duì)象稻励,如果屬性是可寫的父阻,那么可以修改屬性值愈涩。

Object.freeze方法生成的凍結(jié)對(duì)象,屬性都是不可寫的加矛,也就是屬性值無法更改履婉。

  1. in 運(yùn)算符和 Object.hasOwnProperty 方法有什么區(qū)別?
    如你所知斟览,這兩個(gè)特性都檢查對(duì)象中是否存在屬性毁腿,它將返回true或false。它們之間的區(qū)別在于苛茂,in操作符還會(huì)檢查對(duì)象的原型鏈已烤,如果屬性在當(dāng)前對(duì)象中沒有找到,而hasOwnProperty方法只檢查屬性是否存在于當(dāng)前對(duì)象中妓羊,而忽略原型鏈胯究。

hasOwnPropert方法

hasOwnPropert()方法返回值是一個(gè)布爾值,指示對(duì)象自身屬性中是否具有指定的屬性躁绸,因此這個(gè)方法會(huì)忽略掉那些從原型鏈上繼承到的屬性裕循。

看下面的例子:

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方法會(huì)直接忽略掉剥哑。

in 運(yùn)算符

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

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

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

可以看到in運(yùn)算符會(huì)檢查它或者其原型鏈?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("注意:我會(huì)被提升");
}

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

notHoistedFunc調(diào)用拋出異常:Uncaught TypeError: notHoistedFunc is not a function督暂,而hoistedFunc調(diào)用不會(huì),因?yàn)閔oistedFunc會(huì)被提升到作用域的頂部穷吮,而notHoistedFunc 不會(huì)逻翁。

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 對(duì)象(非嚴(yán)格模式)

//Global Scope

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

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

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

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

作為方法調(diào)用——如果一個(gè)對(duì)象的屬性有一個(gè)函數(shù)的值乍迄,我們就稱它為方法管引。調(diào)用該方法時(shí),該方法的this值指向該對(duì)象闯两。

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ù)里面會(huì)默認(rèn)創(chuàng)建一個(gè)空對(duì)象,并將this指向該對(duì)象重慢。

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值饥臂,我們可以使用這些方法,這些方法對(duì)所有函數(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 對(duì)象將是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函數(shù)中的 this 對(duì)象將是 obj2

67. 什么是緩存及它有什么作用隅熙?

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

68. 手動(dòng)實(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)建對(duì)象在JavaScript中。

下面看看例子:

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

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

new關(guān)鍵字做了4件事:
創(chuàng)建空對(duì)象 {}
將空對(duì)象分配給 this 值
將空對(duì)象的proto指向構(gòu)造函數(shù)的prototype
如果沒有使用顯式return語句裂垦,則返回this
根據(jù)上面描述的顺囊,它將首先創(chuàng)建一個(gè)空對(duì)象{},然后它將this值賦給這個(gè)空對(duì)象this={}蕉拢,并向這個(gè)對(duì)象添加屬性特碳。因?yàn)槲覀儧]有顯式的return語句,所以它會(huì)自動(dòng)為我們返回this晕换。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末午乓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闸准,更是在濱河造成了極大的恐慌益愈,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夷家,死亡現(xiàn)場離奇詭異蒸其,居然都是意外死亡或辖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門枣接,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缺谴,你說我怎么就攤上這事但惶。” “怎么了湿蛔?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵膀曾,是天一觀的道長。 經(jīng)常有香客問我阳啥,道長添谊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任察迟,我火速辦了婚禮斩狱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扎瓶。我一直安慰自己所踊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布概荷。 她就那樣靜靜地躺著秕岛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪误证。 梳的紋絲不亂的頭發(fā)上继薛,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天窍育,我揣著相機(jī)與錄音玻粪,去河邊找鬼脆贵。 笑死神僵,一個(gè)胖子當(dāng)著我的面吹牛确镊,可吹牛的內(nèi)容都是我干的筛璧。 我是一名探鬼主播先慷,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼欠雌,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼像棘!你這毒婦竟也來了稽亏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤缕题,失蹤者是張志新(化名)和其女友劉穎截歉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烟零,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘪松,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年咸作,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宵睦。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡记罚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出壳嚎,到底是詐尸還是另有隱情桐智,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布烟馅,位于F島的核電站说庭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏郑趁。R本人自食惡果不足惜刊驴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寡润。 院中可真熱鬧捆憎,春花似錦、人聲如沸梭纹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栗柒。三九已至礁扮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞬沦,已是汗流浹背太伊。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逛钻,地道東北人僚焦。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像曙痘,于是被迫代替她去往敵國和親芳悲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349