https://github.com/yisainan/web-interview/edit/master/content/js/js.md
現在開發主要都是搞 vue react 這些了, dom 有能力就學, 沒能力就先放著,不熟也影響不大
js 的面向對象能學就學, 沒能力就先學學原型鏈, class 和繼承這些可以先放放
## document load 和 document ready 的區別 ***
```
頁面加載完成有兩種事件
1.load是當頁面所有資源全部加載完成后(包括DOM文檔樹,css文件,js文件,圖片資源等),執行一個函數
問題:如果圖片資源較多,加載時間較長,onload后等待執行的函數需要等待較長時間,所以一些效果可能受到影響
2.$(document).ready()是當DOM文檔樹加載完成后執行一個函數 (不包含圖片,css等)所以會比load較快執行
在原生的js中不包括ready()這個方法,只有load方法也就是onload事件
```
## JavaScript 中如何檢測一個變量是一個 String 類型?****
```
參考答案:三種方法(typeof、constructor、Object.prototype.toString.call())
解析:
①
typeof
typeof('123') === "string" // true
typeof '123' === "string" // true
②
constructor
'123'.constructor === String // true
③ Object.prototype.toString.call()
Object.prototype.toString.call('123') === '[object String]' // true
```
## 請用 js 去除字符串空格? **
面試能說個就行
參考答案:replace 正則匹配方法、str.trim()方法、JQ 方法:$.trim(str)方法
解析:
方法一:replace 正則匹配方法
去除字符串內所有的空格:str = str.replace(/\\s\*/g, "");
去除字符串內兩頭的空格:str = str.replace(/^\\s\*|\\s\*$/g, "");
去除字符串內左側的空格:str = str.replace(/^\\s\*/, "");
去除字符串內右側的空格:str = str.replace(/(\\s\*$)/g, "");
示例:
~~~js
var str = " 6 6 ";
var str_1 = str.replace(/\s*/g, "");
console.log(str_1); //66
var str = " 6 6 ";
var str_1 = str.replace(/^\s*|\s*$/g, "");
console.log(str_1); //6 6//輸出左右側均無空格
var str = " 6 6 ";
var str_1 = str.replace(/^\s*/, "");
console.log(str_1); //6 6 //輸出右側有空格左側無空格
var str = " 6 6 ";
var str_1 = str.replace(/(\s*$)/g, "");
console.log(str_1); // 6 6//輸出左側有空格右側無空格
~~~
方法二:str.trim()方法
trim()方法是用來刪除字符串兩端的空白字符并返回,trim 方法并不影響原來的字符串本身,它返回的是一個新的字符串。
缺陷:只能去除字符串兩端的空格,不能去除中間的空格
示例:
~~~js
var str = " 6 6 ";
var str_1 = str.trim();
console.log(str_1); //6 6//輸出左右側均無空格
~~~
方法三:JQ 方法:$.trim(str)方法
$.trim() 函數用于去除字符串兩端的空白字符。
注意:$.trim()函數會移除字符串開始和末尾處的所有換行符,空格(包括連續的空格)和制表符。如果這些空白字符在字符串中間時,它們將被保留,不會被移除。
示例:
~~~js
var str = " 6 6 ";
var str_1 = $.trim(str);
console.log(str_1); //6 6//輸出左右側均無空格
~~~
## == 和 === 的不同 *****
參考答案:`==`是抽象相等運算符,而`===`是嚴格相等運算符。`==`運算符是在進行必要的類型轉換后,再比較。`===`運算符不會進行類型轉換,所以如果兩個值不是相同的類型,會直接返回`false`。使用`==`時,可能發生一些特別的事情,例如:
~~~js
1 == "1"; // true
1 == [1]; // true
1 == true; // true
0 == ""; // true
0 == "0"; // true
0 == false; // true
~~~
如果你對`==`和`===`的概念不是特別了解,建議大多數情況下使用`===`
## 怎樣添加、移除、移動、復制、創建和查找節點?*
參考答案:
1)創建新節點
createDocumentFragment() //創建一個 DOM 片段
createElement() //創建一個具體的元素
createTextNode() //創建一個文本節點
2)添加、移除、替換、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替換
insertBefore() //插入
3)查找
getElementsByTagName() //通過標簽名稱
getElementsByName() //通過元素的 Name 屬性的值
getElementById() //通過元素 Id,唯一性
### 事件委托是什么 ****
參考答案:利用事件冒泡的原理,讓自己的所觸發的事件,讓他的父元素代替執行!
解析:
1、那什么樣的事件可以用事件委托,什么樣的事件不可以用呢?
* 適合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
* 值得注意的是,mouseover 和 mouseout 雖然也有事件冒泡,但是處理它們的時候需要特別的注意,因為需要經常計算它們的位置,處理起來不太容易。
* 不適合的就有很多了,舉個例子,mousemove,每次都要計算它的位置,非常不好把控,在不如說 focus,blur 之類的,本身就沒用冒泡的特性,自然就不用事件委托了。
2、為什么要用事件委托
* 1.提高性能
~~~
<ul>
?<li>蘋果</li>
?<li>香蕉</li>
?<li>鳳梨</li>
</ul>
// good
document.querySelector('ul').onclick = (event) => {
let target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
~~~
* 2.新添加的元素還會有之前的事件。
3、事件冒泡與事件委托的對比
* 事件冒泡:box 內部無論是什么元素,點擊后都會觸發 box 的點擊事件
* 事件委托:可以對 box 內部的元素進行篩選
4、事件委托怎么取索引?
~~~html
<ul id="ul">
<li> aaaaaaaa </li>
<li> 事件委托了 點擊當前, 如何獲取 這個點擊的下標 </li>
<li> cccccccc </li>
</ul>
~~~
~~~js
window.onload = function() {
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName("li");
oUl.onclick = function(ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() == "li") {
var that = target;
var index;
for (var i = 0; i < aLi.length; i++)
if (aLi[i] === target) index = i;
if (index >= 0) alert('我的下標是第' + index + '個');
target.style.background = "red";
}
}
}
~~~
拓展:
* 鍵盤事件:keydown keypress keyup
* 鼠標事件:mousedown mouseup mousemove mouseout mouseover
[參考](https://github.com/qiilee/js/tree/master/JS/%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98)
## require 與 import 的區別 ****
參考答案:兩者的加載方式不同、規范不同
第一、兩者的加載方式不同,require 是在運行時加載,而 import 是在編譯時加載
require('./a')(); // a 模塊是一個函數,立即執行 a 模塊函數
var data = require('./a').data; // a 模塊導出的是一個對象
var a = require('./a')\[0\]; // a 模塊導出的是一個數組 ======> 哪都行
import $ from 'jquery';
import \* as \_ from '\_';
import {a, b, c} from './a';
import {default as alias, a as a\_a, b, c} from './a'; ======>用在開頭
第二、規范不同,require 是 CommonJS/AMD 規范,import 是 ESMAScript6+規范
第三、require 特點:社區方案,提供了服務器/瀏覽器的模塊加載方案。非語言層面的標準。只能在運行時確定模塊的依賴關系及輸入/輸出的變量,無法進行靜態優化。
import 特點:語言規格層面支持模塊功能。支持編譯時靜態分析,便于 JS 引入宏和類型檢驗。動態綁定。
## javascript 對象的幾種創建方式 ***
參考答案:
第一種:Object 構造函數創建
~~~js
var Person = new Object();
Person.name = "Nike";
Person.age = 29;
~~~
這行代碼創建了 Object 引用類型的一個新實例,然后把實例保存在變量 Person 中。
第二種:使用對象字面量表示法
~~~js
var Person = {}; //相當于 var Person = new Object();
var Person = {
name: 'Nike';
age: 29;
}
~~~
對象字面量是對象定義的一種簡寫形式,目的在于簡化創建包含大量屬性的對象的過程。也就是說,第一種和第二種方式創建對象的方法其實都是一樣的,只是寫法上的區別不同
在介紹第三種的創建方法之前,我們應該要明白為什么還要用別的方法來創建對象,也就是第一種,第二種方法的缺點所在:它們都是用了同一個接口創建很多對象,會產生大量的重復代碼,就是如果你有 100 個對象,那你要輸入 100 次很多相同的代碼。那我們有什么方法來避免過多的重復代碼呢,就是把創建對象的過程封裝在函數體內,通過函數的調用直接生成對象。
第三種:使用工廠模式創建對象
~~~js
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person1 = createPerson("Nike", 29, "teacher");
var person2 = createPerson("Arvin", 20, "student");
~~~
在使用工廠模式創建對象的時候,我們都可以注意到,在 createPerson 函數中,返回的是一個對象。那么我們就無法判斷返回的對象究竟是一個什么樣的類型。于是就出現了第四種創建對象的模式。
第四種: 使用構造函數創建對象
~~~js
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person("Nike", 29, "teacher");
var person2 = new Person("Arvin", 20, "student");
~~~
對比工廠模式,我們可以發現以下區別:
1.沒有顯示地創建對象
2.直接將屬性和方法賦給了 this 對象
3.沒有 return 語句
4.終于可以識別的對象的類型。對于檢測對象類型,我們應該使用 instanceof 操作符,我們來進行自主檢測:
~~~js
alert(person1 instanceof Object); //ture
alert(person1 instanceof Person); //ture
alert(person2 instanceof Object); //ture
alert(person2 instanceof Object); //ture
~~~
同時我們也應該明白,按照慣例,構造函數始終要應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。
那么構造函數確實挺好用的,但是它也有它的缺點:
就是每個方法都要在每個實例上重新創建一遍,方法指的就是我們在對象里面定義的函數。如果方法的數量很多,就會占用很多不必要的內存。于是出現了第五種創建對象的方法
第五種:原型創建對象模式
~~~js
function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
person1.sayName();
~~~
使用原型創建對象的方式,可以讓所有對象實例共享它所包含的屬性和方法。
如果是使用原型創建對象模式,請看下面代碼:
~~~js
function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //'Greg' --來自實例
alert(person2.name); //'Nike' --來自原型
~~~
當為對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。
這時候我們就可以使用構造函數模式與原型模式結合的方式,構造函數模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性
第六種:組合使用構造函數模式和原型模式
~~~js
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
};
}
var person1 = new Person('Nike', 20, 'teacher');
~~~
解析:[參考](https://zhidao.baidu.com/question/1180348878138910499.html)
## JavaScript 繼承的方式和優缺點 ***
參考答案:六種方式
* 一、原型鏈繼承
* 缺點:
* 1.引用類型的屬性被所有實例共享
* 2.在創建 Child 的實例時,不能向 Parent 傳參
* 二、借用構造函數(經典繼承)
* 優點:
* 1.避免了引用類型的屬性被所有實例共享
* 2.可以在 Child 中向 Parent 傳參
* 缺點:
* 1.方法都在構造函數中定義,每次創建實例都會創建一遍方法。
* 三、組合繼承
* 優點:
* 1.融合原型鏈繼承和構造函數的優點,是 JavaScript 中最常用的繼承模式。
* 四、原型式繼承
* 缺點:
* 1.包含引用類型的屬性值始終都會共享相應的值,這點跟原型鏈繼承一樣。
* 五、寄生式繼承
* 缺點:
* 1.跟借用構造函數模式一樣,每次創建對象都會創建一遍方法。
* 六、寄生組合式繼承
* 優點:
* 1.這種方式的高效率體現它只調用了一次 Parent 構造函數,并且因此避免了在 Parent.prototype 上面創建不必要的、多余的屬性。
* 2.與此同時,原型鏈還能保持不變;
* 3.因此,還能夠正常使用 instanceof 和 isPrototypeOf。
* 開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承范式
解析:[參考](https://www.jianshu.com/p/09ad43c7fe8f)
## 什么是原型鏈?*****
參考答案:通過一個對象的\_\_proto\_\_可以找到它的原型對象,原型對象也是一個對象,就可以通過原型對象的\_\_proto\_\_,最后找到了我們的 Object.prototype, 從實例的原型對象開始一直到 Object.prototype 就是我們的原型鏈
解析:
[](https://github.com/yisainan/web-interview/blob/master/images/js_001.png)
## 復雜數據類型如何轉變為字符串 ****
參考答案:
* 首先,會調用 valueOf 方法,如果方法的返回值是一個基本數據類型,就返回這個值,
* 如果調用 valueOf 方法之后的返回值仍舊是一個復雜數據類型,就會調用該對象的 toString 方法,
* 如果 toString 方法調用之后的返回值是一個基本數據類型,就返回這個值,
* 如果 toString 方法調用之后的返回值是一個復雜數據類型,就報一個錯誤。
解析:
~~~js
// 1;
var obj = {
valueOf: function() {
return 1;
}
};
console.log(obj + ""); //'1'
// 2;
var obj = {
valueOf: function() {
return [1, 2];
}
};
console.log(obj + ""); //'[object Object]';
// 3;
var obj = {
valueOf: function() {
return [1, 2];
},
toString: function() {
return 1;
}
};
console.log(obj + ""); //'1';
// 4;
var obj = {
valueOf: function() {
return [1, 2];
},
toString: function() {
return [1, 2, 3];
}
};
console.log(obj + ""); // 報錯 Uncaught TypeError: Cannot convert object to primitive value
~~~
拓展:
~~~js
var arr = [new Object(), new Date(), new RegExp(), new String(), new Number(), new Boolean(), new Function(), new Array(), Math] console.log(arr.length) // 9
for (var i = 0; i < arr.length; i++) {
arr[i].valueOf = function() {
return [1, 2, 3]
}
arr[i].toString = function() {
return 'toString'
}
console.log(arr[i] + '')
}
~~~
1、若 return \[1, 2, 3\]處為 return "valueof",得到的返回值是 valueof toString 7valueof 說明:其他八種復雜數據類型是先調用 valueOf 方法,時間對象是先調用 toString 方法
2、改成 return \[1, 2, 3\],得到的返回值是 9toString 說明:執行 valueof 后都來執行 toString
## javascript 的 typeof 返回哪些數據類型 *****
參考答案:7 種分別為 string、boolean、number、Object、Function、undefined、symbol(ES6)、
示例:
1、number
~~~js
typeof(10);
typeof(NaN); // NaN在JavaScript中代表的是特殊非數字值,它本身是一個數字類型。
typeof(Infinity)
~~~
2、boolean
~~~js
typeof(true);
typeof(false);
~~~
3、string
~~~js
typeof("abc");
~~~
4、undefined
~~~js
typeof(undefined);
typeof(a); // 不存在的變量
~~~
5、object
~~~js
// 對象,數組,null返回object
typeof(null);
typeof(window);
~~~
6、function
~~~js
typeof(Array);
typeof(Date);
~~~
7、symbol
~~~js
typeof Symbol() // ES6提供的新的類型
~~~
## 一次js請求一般情況下有哪些地方會有緩存處理?****
參考答案:DNS緩存,CDN緩存,瀏覽器緩存,服務器緩存。
解析:
#### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#1dns%E7%BC%93%E5%AD%98)1、DNS緩存
DNS緩存指DNS返回了正確的IP之后,系統就會將這個結果臨時儲存起來。并且它會為緩存設定一個失效時間 (例如N小時),在這N小時之內,當你再次訪問這個網站時,系統就會直接從你電腦本地的DNS緩存中把結果交還給你,而不必再去詢問DNS服務器,變相“加速”了網址的解析。當然,在超過N小時之后,系統會自動再次去詢問DNS服務器獲得新的結果。 所以,當你修改了 DNS 服務器,并且不希望電腦繼續使用之前的DNS緩存時,就需要手動去清除本地的緩存了。
本地DNS遲遲不生效或者本地dns異常等問題,都會導致訪問某些網站出現無法訪問的情況,這個時候我們就需要手動清除本地dns緩存,而不是等待!
#### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#2cdn%E7%BC%93%E5%AD%98)2、CDN緩存
和Http類似,客戶端請求數據時,先從本地緩存查找,如果被請求數據沒有過期,拿過來用,如果過期,就向CDN邊緣節點發起請求。CDN便會檢測被請求的數據是否過期,如果沒有過期,就返回數據給客戶端,如果過期,CDN再向源站發送請求獲取新數據。和買家買貨,賣家沒貨,賣家再進貨一個道理^^。
CDN邊緣節點緩存機制,一般都遵守http標準協議,通過http響應頭中的Cache-Control和max-age的字段來設置CDN邊緣節點的數據緩存時間。
#### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#3%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BC%93%E5%AD%98)3、瀏覽器緩存
瀏覽器緩存(Browser Caching)是為了節約網絡的資源加速瀏覽,瀏覽器在用戶磁盤上對最近請求過的文檔進行存儲,當訪問者再次請求這個頁面時,瀏覽器就可以從本地磁盤顯示文檔,這樣就可以加速頁面的閱覽。
瀏覽器緩存主要有兩類:緩存協商:Last-modified ,Etag 和徹底緩存:cache-control,Expires。瀏覽器都有對應清除緩存的方法。
#### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#4%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BC%93%E5%AD%98)4、服務器緩存
服務器緩存有助于優化性能和節省寬帶,它將需要頻繁訪問的Web頁面和對象保存在離用戶更近的系統中,當再次訪問這些對象的時候加快了速度。
## 列舉 3 種強制類型轉換和 2 種隱式類型轉換 ****
參考答案:
強制: parseInt(), parseFloat(), Number(), Boolean(), String()
隱式: +, -
解析:
~~~js
// 1.parseInt() 把值轉換成整數
parseInt("1234blue"); // 1234
parseInt("0xA"); // 10
parseInt("22.5"); // 22
parseInt("blue"); // NaN
// parseInt()方法還有基模式,可以把二進制、八進制、十六進制或其他任何進制的字符串轉換成整數。基是由parseInt()方法的第二個參數指定的,示例如下:
parseInt("AF", 16); // 175
parseInt("10", 2); // 2
parseInt("10", 8); // 8
parseInt("10", 10); // 10
// 如果十進制數包含前導0,那么最好采用基數10,這樣才不會意外地得到八進制的值。例如:
parseInt("010"); // 8
parseInt("010", 8); // 8
parseInt("010", 10); // 10
// 2.parseFloat() 把值轉換成浮點數,沒有基模式
parseFloat("1234blue"); // 1234.0
parseFloat("0xA"); // NaN
parseFloat("22.5"); // 22.5
parseFloat("22.34.5"); // 22.34
parseFloat("0908"); // 908
parseFloat("blue"); // NaN
// 3.Number() 把給定的值轉換成數字(可以是整數或浮點數),Number()的強制類型轉換與parseInt()和parseFloat()方法的處理方式相似,只是它轉換的是整個值,而不是部分值。示例如下:
Number(false) // 0
Number(true) // 1
Number(undefined) // NaN
Number(null) // 0
Number("5.5") // 5.5
Number("56") // 56
Number("5.6.7") // NaN
Number(new Object()) // NaN
Number(100) // 100
// 4.Boolean() 把給定的值轉換成Boolean型
Boolean(""); // false
Boolean("hi"); // true
Boolean(100); // true
Boolean(null); // false
Boolean(0); // false
Boolean(new Object()); // true
// 5.String() 把給定的值轉換成字符串
String(123) // "123"
// 6.+ -
console.log(0 + '1') // "01"
console.log(2 - '1') // 1
~~~
## 你對閉包的理解?優缺點?*****
參考答案:
概念:閉包就是能夠讀取其他函數內部變量的函數。
三大特性:
* 函數嵌套函數。
* 函數內部可以引用外部的參數和變量。
* 參數和變量不會被垃圾回收機制回收。
優點:
* 希望一個變量長期存儲在內存中。
* 避免全局變量的污染。
* 私有成員的存在。
缺點:
* 常駐內存,增加內存使用量。
* 使用不當會很容易造成內存泄露。
示例:
~~~js
function outer() {
var name = "jack";
function inner() {
console.log(name);
}
return inner;
}
outer()(); // jack
~~~
~~~js
function sayHi(name) {
return () => {
console.log(`Hi! ${name}`);
};
}
const test = sayHi("xiaoming");
test(); // Hi! xiaoming
~~~
雖然 sayHi 函數已經執行完畢,但是其活動對象也不會被銷毀,因為 test 函數仍然引用著 sayHi 函數中的變量 name,這就是閉包。
但也因為閉包引用著另一個函數的變量,導致另一個函數已經不使用了也無法銷毀,所以閉包使用過多,會占用較多的內存,這也是一個副作用。
解析:
由于在 ECMA2015 中,只有函數才能分割作用域,函數內部可以訪問當前作用域的變量,但是外部無法訪問函數內部的變量,所以閉包可以理解成“定義在一個函數內部的函數,外部可以通過內部返回的函數訪問內部函數的變量“。在本質上,閉包是將函數內部和函數外部連接起來的橋梁。
## 如何判斷 NaN *****
參考答案:isNaN()方法
解析:isNaN(NaN) // true
## new 一個對象的過程中發生了什么
參考答案:
~~~js
function Person(name) {
this.name = name;
}
var person = new Person("qilei");
~~~
new一個對象的四個過程:
~~~js
// 1.創建空對象;
var obj = {};
// 2.設置原型鏈: 設置新對象的 constructor 屬性為構造函數的名稱,設置新對象的__proto__屬性指向構造函數的 prototype 對象;
obj.constructor = Person;
obj.__proto__ = Person.prototype;
// 3.改變this指向:使用新對象調用函數,函數中的 this 指向新實例對象obj:
var result = Person.call(obj); //{}.構造函數();
// 4.返回值:如果無返回值或者返回一個非對象值,則將新對象返回;如果返回值是一個新對象的話那么直接返回該對象。
if (typeof(result) == "object") {
person = result;
} else {
person = obj;
}
~~~
## for in 和 for of的區別 ****
參考答案:
1、for in
* 1.一般用于遍歷對象的可枚舉屬性。以及對象從構造函數原型中繼承的屬性。對于每個不同的屬性,語句都會被執行。
* 2.不建議使用 for in 遍歷數組,因為輸出的順序是不固定的。
* 3.如果迭代的對象的變量值是 null 或者 undefined, for in 不執行循環體,建議在使用 for in 循環之前,先檢查該對象的值是不是 null 或者 undefined。
2、for of
* 1.for…of 語句在可迭代對象(包括 Array,Map,Set,String,TypedArray,arguments 對象等等)上創建一個迭代循環,調用自定義迭代鉤子,并為每個不同屬性的值執行語句。
解析:
~~~js
var s = {
a: 1,
b: 2,
c: 3
};
var s1 = Object.create(s);
for (var prop in s1) {
console.log(prop); //a b c
console.log(s1[prop]); //1 2 3
}
for (let prop of s1) {
console.log(prop); //報錯如下 Uncaught TypeError: s1 is not iterable
}
for (let prop of Object.keys(s1)) {
console.log(prop); // a b c
console.log(s1[prop]); //1 2 3
}
~~~
## 如何判斷 JS 變量的一個類型(至少三種方式)***
參考答案:typeof、instanceof、 constructor、 prototype
解析:
1、typeof
typeof 返回一個表示數據類型的字符串,返回結果包括:number、boolean、string、object、undefined、function等6種數據類型。如果是判斷一個基本的類型用typeof就是可以的。
~~~js
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof null; //object 無效
typeof []; //object 無效
typeof new Function(); // function 有效
typeof new Date(); //object 無效
typeof new RegExp(); //object 無效
~~~
2、instanceof
instanceof 是用來判斷 A 是否為 B 的實例對,表達式為:A instanceof B,如果A是B的實例,則返回true, 否則返回false。 在這里需要特別注意的是:instanceof檢測的是原型,
~~~js
[] instanceof Array; //true
{}
instanceof Object; //true
new Date() instanceof Date; //true
~~~
3、constractor
每一個對象實例都可以通過 constrcutor 對象來訪問它的構造函數 。JS 中內置了一些構造函數:Object、Array、Function、Date、RegExp、String等。我們可以通過數據的 constrcutor 是否與其構造函數相等來判斷數據的類型。
~~~js
var arr = [];
var obj = {};
var date = new Date();
var num = 110;
var str = 'Hello';
var getName = function() {};
var sym = Symbol();
var set = new Set();
var map = new Map();
arr.constructor === Array; // true
obj.constructor === Object; // true
date.constructor === Date; // true
str.constructor === String; // true
getName.constructor === Function; // true
sym.constructor === Symbol; // true
set.constructor === Set; // true
map.constructor === Map // true
~~~
4、Object.prototype.toString
toString是Object原型對象上的一個方法,該方法默認返回其調用者的具體類型,更嚴格的講,是 toString運行時this指向的對象類型, 返回的類型格式為\[object, xxx\], xxx是具體的數據類型,其中包括:String, Number, Boolean, Undefined, Null, Function, Date, Array, RegExp, Error, HTMLDocument, ...基本上所有對象的類型都可以通過這個方法獲取到。
~~~js
Object.prototype.toString.call(''); // [object String]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(new Function()); // [object Function]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(new RegExp()); // [object RegExp]
Object.prototype.toString.call(new Error()); // [object Error]
~~~
## for in、Object.keys 和 Object.getOwnPropertyNames 對屬性遍歷有什么區別?****
參考答案:
* for in 會遍歷自身及原型鏈上的可枚舉屬性
* Object.keys 會將對象自身的可枚舉屬性的 key 輸出
* Object.getOwnPropertyNames會將自身所有的屬性的 key 輸出
解析:
ECMAScript 將對象的屬性分為兩種:數據屬性和訪問器屬性。
~~~js
var parent = Object.create(Object.prototype, {
a: {
value: 123,
writable: true,
enumerable: true,
configurable: true
}
});
// parent繼承自Object.prototype,有一個可枚舉的屬性a(enumerable:true)。
var child = Object.create(parent, {
b: {
value: 2,
writable: true,
enumerable: true,
configurable: true
},
c: {
value: 3,
writable: true,
enumerable: false,
configurable: true
}
});
//child 繼承自 parent ,b可枚舉,c不可枚舉
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#for-in)for in
~~~js
for (var key in child) {
console.log(key);
}
// b
// a
// for in 會遍歷自身及原型鏈上的可枚舉屬性
~~~
如果只想輸出自身的可枚舉屬性,可使用 hasOwnProperty 進行判斷(數組與對象都可以,此處用數組做例子)
~~~js
let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(arr[i]);
}
}
// 1
// 2
// 3
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#objectkeys)Object.keys
~~~js
console.log(Object.keys(child));
// ["b"]
// Object.keys 會將對象自身的可枚舉屬性的key輸出
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#objectgetownpropertynames)Object.getOwnPropertyNames
~~~js
console.log(Object.getOwnPropertyNames(child));
// ["b","c"]
// 會將自身所有的屬性的key輸出
~~~
## iframe 跨域通信和不跨域通信
參考答案:
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#%E4%B8%8D%E8%B7%A8%E5%9F%9F%E9%80%9A%E4%BF%A1)不跨域通信
主頁面
~~~html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<iframe name="myIframe" id="iframe" class="" src="flexible.html" width="500px" height="500px">
</iframe>
</body>
<script type="text/javascript" charset="utf-8">
function fullscreen() {
alert(1111);
}
</script>
</html>
~~~
子頁面 flexible.html
~~~html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
我是子頁面
</body>
<script type="text/javascript" charset="utf-8">
// window.parent.fullScreens()
function showalert() {
alert(222);
}
</script>
</html>
~~~
1、主頁面要是想要調取子頁面的 showalert 方法
~~~js
myIframe.window.showalert();
~~~
2、子頁面要掉主頁面的 fullscreen 方法
~~~js
window.parent.fullScreens();
~~~
3、js 在 iframe 子頁面獲取父頁面元素:
~~~js
window.parent.document.getElementById("元素id");
~~~
4、js 在父頁面獲取 iframe 子頁面元素代碼如下:
~~~js
window.frames["iframe_ID"].document.getElementById("元素id");
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#%E8%B7%A8%E5%9F%9F%E9%80%9A%E4%BF%A1)跨域通信
使用[postMessage(官方用法)](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage)
子頁面
~~~js
window.parent.postMessage("hello", "http://127.0.0.1:8089");
~~~
父頁面接收
~~~js
window.addEventListener("message", function(event) {
alert(123);
});
~~~
解析:[參考](https://blog.csdn.net/weixin_41229588/article/details/93719894)
## 如何判斷一個對象是否為數組 ***
參考答案:
第一種方法:使用 instanceof 操作符。
第二種方法:使用 ECMAScript 5 新增的 Array.isArray()方法。
第三種方法:使用使用 Object.prototype 上的原生 toString()方法判斷。
## script 標簽的 defer 和 asnyc 屬性的作用以及二者的區別?
參考答案:
* 1、defer 和 async 的網絡加載過程是一致的,都是異步執行。
* 2、區別在于加載完成之后什么時候執行,可以看出 defer 是文檔所有元素解析完成之后才執行的。
* 3、如果存在多個 defer 腳本,那么它們是按照順序執行腳本的,而 async,無論聲明順序如何,只要加載完成就立刻執行
解析:
無論`<script>`標簽是嵌入代碼還是引用外部文件,只要不包含 defer 屬性和 async 屬性(這兩個屬性只對外部文件有效),瀏覽器會按照`<script>`的出現順序對他們依次進行解析,也就是說,只有在第一個`<script>`中的代碼執行完成之后,瀏覽器才會執行第二個`<script>`中的代碼,并且在解析時,頁面的處理會暫時停止。
嵌入代碼的解析=執行 外部文件的解析=下載+執行
script 標簽存在兩個屬性,defer 和 async,這兩個屬性只對外部文件有效
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E8%84%9A%E6%9C%AC%E7%9A%84%E6%83%85%E5%86%B5)只有一個腳本的情況
~~~
<script src = "a.js" />
~~~
沒有 defer 或 async 屬性,瀏覽器會立即下載并執行相應的腳本,并且在下載和執行時頁面的處理會停止。
~~~
<script defer src = "a.js" />
~~~
有了 defer 屬性,瀏覽器會立即下載相應的腳本,在下載的過程中頁面的處理不會停止,等到文檔解析完成腳本才會執行。
~~~
<script async src = "a.js" />
~~~
有了 async 屬性,瀏覽器會立即下載相應的腳本,在下載的過程中頁面的處理不會停止,下載完成后立即執行,執行過程中頁面處理會停止。
~~~
<script defer async src = "a.js" />
~~~
如果同時指定了兩個屬性, 則會遵從 async 屬性而忽略 defer 屬性。
下圖可以直觀的看出三者之間的區別:
[](https://github.com/yisainan/web-interview/blob/master/images/js_002.png)
其中藍色代表 js 腳本網絡下載時間,紅色代表 js 腳本執行,綠色代表 html 解析。
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#%E5%A4%9A%E4%B8%AA%E8%84%9A%E6%9C%AC%E7%9A%84%E6%83%85%E5%86%B5)多個腳本的情況
這里只列舉兩個腳本的情況:
~~~
<script src = "a.js"> </script>
<script src = "b.js"> </script>
~~~
沒有 defer 或 async 屬性,瀏覽器會立即下載并執行腳本 a.js,在 a.js 腳本執行完成后才會下載并執行腳本 b.js,在腳本下載和執行時頁面的處理會停止。
~~~
<script defer src = "a.js"> </script>
<script defer src = "b.js"> </script>
~~~
有了 defer 屬性,瀏覽器會立即下載相應的腳本 a.js 和 b.js,在下載的過程中頁面的處理不會停止,等到文檔解析完成才會執行這兩個腳本。HTML5 規范要求腳本按照它們出現的先后順序執行,因此第一個延遲腳本會先于第二個延遲腳本執行,而這兩個腳本會先于 DOMContentLoaded 事件執行。 在現實當中,延遲腳本并不一定會按照順序執行,也不一定會在 DOMContentLoaded 事件觸發前執行,因此最好只包含一個延遲腳本。
~~~
<script async src = "a.js"> </script>
<script async src = "b.js"> </script>
~~~
有了 async 屬性,瀏覽器會立即下載相應的腳本 a.js 和 b.js,在下載的過程中頁面的處理不會停止,a.js 和 b.js 哪個先下載完成哪個就立即執行,執行過程中頁面處理會停止,但是其他腳本的下載不會停止。標記為 async 的腳本并不保證按照制定它們的先后順序執行。異步腳本一定會在頁面的 load 事件前執行,但可能會在 DOMContentLoaded 事件觸發之前或之后執行。
[參考](https://blog.csdn.net/weixin_42561383/article/details/86564715)
## Object.prototype.toString.call() 和 instanceOf 和 Array.isArray() 區別好壞 ***
參考答案:
* Object.prototype.toString.call()
* 優點:這種方法對于所有基本的數據類型都能進行判斷,即使是 null 和 undefined 。
* 缺點:不能精準判斷自定義對象,對于自定義對象只會返回\[object Object\]
* instanceOf
* 優點:instanceof 可以彌補 Object.prototype.toString.call()不能判斷自定義實例化對象的缺點。
* 缺點: instanceof 只能用來判斷對象類型,原始類型不可以。并且所有對象類型 instanceof Object 都是 true,且不同于其他兩種方法的是它不能檢測出 iframes。
* Array.isArray()
* 優點:當檢測 Array 實例時,Array.isArray 優于 instanceof ,因為 Array.isArray 可以檢測出 iframes
* 缺點:只能判別數組
解析:
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#objectprototypetostringcall)Object.prototype.toString.call()
每一個繼承 Object 的對象都有 toString 方法,如果 toString 方法沒有重寫的話,會返回 \[Object type\],其中 type 為對象的類型。但當除了 Object 類型的對象外,其他類型直接使用 toString 方法時,會直接返回都是內容的字符串,所以我們需要使用 call 或者 apply 方法來改變 toString 方法的執行上下文。
~~~js
const an = ["Hello", "An"];
an.toString(); // "Hello,An"
Object.prototype.toString.call(an); // "[object Array]"
~~~
這種方法對于所有基本的數據類型都能進行判斷,即使是 null 和 undefined 。
~~~js
Object.prototype.toString.call("An"); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function() {}); // "[object Function]"
Object.prototype.toString.call({
name: "An"
}); // "[object Object]"
~~~
缺點:不能精準判斷自定義對象,對于自定義對象只會返回\[object Object\]
~~~js
function f(name) {
this.name = name;
}
var f1 = new f("martin");
console.log(Object.prototype.toString.call(f1)); //[object Object]
Object.prototype.toString.call(); // 常用于判斷瀏覽器內置對象。
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#instanceof)instanceof
instanceof 的內部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype。
使用 instanceof 判斷一個對象是否為數組,instanceof 會判斷這個對象的原型鏈上是否會找到對應的 Array 的原型,找到返回 true,否則返回 false。
~~~js
[] instanceof Array; // true
~~~
但 instanceof 只能用來判斷對象類型,原始類型不可以。并且所有對象類型 instanceof Object 都是 true。
~~~js
[] instanceof Object; // true
~~~
優點:instanceof 可以彌補 Object.prototype.toString.call()不能判斷自定義實例化對象的缺點。
缺點:instanceof 只能用來判斷對象類型,原始類型不可以。并且所有對象類型 instanceof Object 都是 true,且不同于其他兩種方法的是它不能檢測出 iframes。
~~~js
function f(name) {
this.name = name;
}
var f1 = new f("martin");
console.log(f1 instanceof f); //true
~~~
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#arrayisarray)Array.isArray()
* 功能:用來判斷對象是否為數組
* instanceof 與 isArray
當檢測 Array 實例時,Array.isArray 優于 instanceof ,因為 Array.isArray 可以檢測出 iframes
~~~js
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length - 1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
~~~
缺點:只能判別數組
* Array.isArray() 與 Object.prototype.toString.call()
Array.isArray()是 ES5 新增的方法,當不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 實現。
~~~js
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
~~~
[參考](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/23)
## JS 嚴格模式和正常模式 ***
這個寫的太詳細了, 剛學前端會說個作用就可以
參考答案:嚴格模式使用"use strict";
作用:
* 消除 Javascript 語法的一些不合理、不嚴謹之處,減少一些怪異行為;
* 消除代碼運行的一些不安全之處,保證代碼運行的安全;
* 提高編譯器效率,增加運行速度;
* 為未來新版本的 Javascript 做好鋪墊。
表現:
* 嚴格模式下, delete 運算符后跟隨非法標識符(即 delete 不存在的標識符),會拋出語法錯誤; 非嚴格模式下,會靜默失敗并返回 false
* 嚴格模式中,對象直接量中定義同名屬性會拋出語法錯誤; 非嚴格模式不會報錯
* 嚴格模式中,函數形參存在同名的,拋出錯誤; 非嚴格模式不會
* 嚴格模式不允許八進制整數直接量(如:023)
* 嚴格模式中,arguments 對象是傳入函數內實參列表的靜態副本;非嚴格模式下,arguments 對象里的元素和對應的實參是指向同一個值的引用
* 嚴格模式中 eval 和 arguments 當做關鍵字,它們不能被賦值和用作變量聲明
* 嚴格模式會限制對調用棧的檢測能力,訪問 arguments.callee.caller 會拋出異常
* 嚴格模式 變量必須先聲明,直接給變量賦值,不會隱式創建全局變量,不能用 with,
* 嚴格模式中 call apply 傳入 null undefined 保持原樣不被轉換為 window
解析:
一、概述
除了正常運行模式,ECMAscript 5 添加了第二種運行模式:"嚴格模式"(strict mode)。顧名思義,這種模式使得 Javascript 在更嚴格的條件下運行。
設立"嚴格模式"的目的,主要有以下幾個:
* 消除 Javascript 語法的一些不合理、不嚴謹之處,減少一些怪異行為;
* 消除代碼運行的一些不安全之處,保證代碼運行的安全;
* 提高編譯器效率,增加運行速度;
* 為未來新版本的 Javascript 做好鋪墊。
"嚴格模式"體現了 Javascript 更合理、更安全、更嚴謹的發展方向,包括 IE 10 在內的主流瀏覽器,都已經支持它,許多大項目已經開始全面擁抱它。
另一方面,同樣的代碼,在"嚴格模式"中,可能會有不一樣的運行結果;一些在"正常模式"下可以運行的語句,在"嚴格模式"下將不能運行。掌握這些內容,有助于更細致深入地理解 Javascript,讓你變成一個更好的程序員。
本文將對"嚴格模式"做詳細介紹。
二、進入標志
進入"嚴格模式"的標志,是下面這行語句:
"use strict";
老版本的瀏覽器會把它當作一行普通字符串,加以忽略。
三、如何調用
"嚴格模式"有兩種調用方法,適用于不同的場合。
3.1 針對整個腳本文件
將"use strict"放在腳本文件的第一行,則整個腳本都將以"嚴格模式"運行。如果這行語句不在第一行,則無效,整個腳本以"正常模式"運行。如果不同模式的代碼文件合并成一個文件,這一點需要特別注意。
(嚴格地說,只要前面不是產生實際運行結果的語句,"use strict"可以不在第一行,比如直接跟在一個空的分號后面。)
~~~
<script>
"use strict";
console.log("這是嚴格模式。");
</script>
<script>
console.log("這是正常模式。");kly, it's almost 2 years ago now.I can admit it now - I run it on my school's network that has about 50 computers.
</script>
~~~
上面的代碼表示,一個網頁中依次有兩段 Javascript 代碼。前一個 script 標簽是嚴格模式,后一個不是。
3.2 針對單個函數
將"use strict"放在函數體的第一行,則整個函數以"嚴格模式"運行。
~~~js
function strict() {
"use strict";
return "這是嚴格模式。";
}
function notStrict() {
return "這是正常模式。";
}
~~~
3.3 腳本文件的變通寫法
因為第一種調用方法不利于文件合并,所以更好的做法是,借用第二種方法,將整個腳本文件放在一個立即執行的匿名函數之中。
~~~js
(function() {
"use strict"; // some code here
})();
~~~
四、語法和行為改變
嚴格模式對 Javascript 的語法和行為,都做了一些改變。
4.1 全局變量顯式聲明
在正常模式中,如果一個變量沒有聲明就賦值,默認是全局變量。嚴格模式禁止這種用法,全局變量必須顯式聲明。
~~~js
"use strict";
v = 1; // 報錯,v未聲明
for (i = 0; i < 2; i++) {
// 報錯,i未聲明
}
~~~
因此,嚴格模式下,變量都必須先用 var 命令聲明,然后再使用。
4.2 靜態綁定
Javascript 語言的一個特點,就是允許"動態綁定",即某些屬性和方法到底屬于哪一個對象,不是在編譯時確定的,而是在運行時(runtime)確定的。
嚴格模式對動態綁定做了一些限制。某些情況下,只允許靜態綁定。也就是說,屬性和方法到底歸屬哪個對象,在編譯階段就確定。這樣做有利于編譯效率的提高,也使得代碼更容易閱讀,更少出現意外。
具體來說,涉及以下幾個方面。
(1)禁止使用 with 語句
因為 with 語句無法在編譯時就確定,屬性到底歸屬哪個對象。
~~~js
"use strict";
var v = 1;
with(o) { // 語法錯誤
v = 2;
}
~~~
(2)創設 eval 作用域
正常模式下,Javascript 語言有兩種變量作用域(scope):全局作用域和函數作用域。嚴格模式創設了第三種作用域:eval 作用域。
正常模式下,eval 語句的作用域,取決于它處于全局作用域,還是處于函數作用域。嚴格模式下,eval 語句本身就是一個作用域,不再能夠生成全局變量了,它所生成的變量只能用于 eval 內部。
~~~js
"use strict";
var x = 2;
console.info(eval("var x = 5; x")); // 5
console.info(x); // 2
~~~
4.3 增強的安全措施
(1)禁止 this 關鍵字指向全局對象
~~~js
function f() {
return !this;
} // 返回false,因為"this"指向全局對象,"!this"就是false
function f() {
"use strict";
return !this;
} // 返回true,因為嚴格模式下,this的值為undefined,所以"!this"為true。
~~~
因此,使用構造函數時,如果忘了加 new,this 不再指向全局對象,而是報錯。
~~~js
function f() {
"use strict";
this.a = 1;
}
f(); // 報錯,this未定義
~~~
(2)禁止在函數內部遍歷調用棧
~~~js
function f1() {
"use strict";
f1.caller; // 報錯
f1.arguments; // 報錯
}
f1();
~~~
4.4 禁止刪除變量
嚴格模式下無法刪除變量。只有 configurable 設置為 true 的對象屬性,才能被刪除。
~~~js
"use strict";
var x;
delete x; // 語法錯誤
var o = Object.create(null, {
'x': {
value: 1,
configurable: true
}
});
delete o.x; // 刪除成功
~~~
4.5 顯式報錯
正常模式下,對一個對象的只讀屬性進行賦值,不會報錯,只會默默地失敗。嚴格模式下,將報錯。
~~~js
"use strict";
var o = {};
Object.defineProperty(o, "v", {
value: 1,
writable: false
});
o.v = 2; // 報錯
~~~
嚴格模式下,對一個使用 getter 方法讀取的屬性進行賦值,會報錯。
~~~js
"use strict";
var o = {
get v() {
return 1;
}
};
o.v = 2; // 報錯
~~~
嚴格模式下,對禁止擴展的對象添加新屬性,會報錯。
~~~js
"use strict";
var o = {};
Object.preventExtensions(o);
o.v = 1; // 報錯
~~~
嚴格模式下,刪除一個不可刪除的屬性,會報錯。
~~~js
"use strict";
delete Object.prototype; // 報錯
~~~
4.6 重名錯誤
嚴格模式新增了一些語法錯誤。
(1)對象不能有重名的屬性
正常模式下,如果對象有多個重名屬性,最后賦值的那個屬性會覆蓋前面的值。嚴格模式下,這屬于語法錯誤。
~~~js
"use strict";
var o = {
p: 1,
p: 2
}; // 語法錯誤
~~~
(2)函數不能有重名的參數
正常模式下,如果函數有多個重名的參數,可以用 arguments\[i\]讀取。嚴格模式下,這屬于語法錯誤。
~~~js
"use strict";
function f(a, a, b) { // 語法錯誤
return;
}
~~~
4.7 禁止八進制表示法
正常模式下,整數的第一位如果是 0,表示這是八進制數,比如 0100 等于十進制的 64。嚴格模式禁止這種表示法,整數第一位為 0,將報錯。
~~~js
"use strict";
var n = 0100; // 語法錯誤
~~~
4.8 arguments 對象的限制
arguments 是函數的參數對象,嚴格模式對它的使用做了限制。
(1)不允許對 arguments 賦值
~~~js
"use strict";
arguments++; // 語法錯誤
var obj = {
set p(arguments) {}
}; // 語法錯誤
try {} catch (arguments) {} // 語法錯誤
function arguments() {} // 語法錯誤
var f = new Function("arguments", "'use strict'; return 17;"); // 語法錯誤
~~~
(2)arguments 不再追蹤參數的變化
~~~js
function f(a) {
a = 2;
return [a, arguments[0]];
}
f(1); // 正常模式為[2,2]
function f(a) {
"use strict";
a = 2;
return [a, arguments[0]];
}
f(1); // 嚴格模式為[2,1]
~~~
(3)禁止使用 arguments.callee
這意味著,你無法在匿名函數內部調用自身了。
~~~js
"use strict";
var f = function() {
return arguments.callee;
};
f(); // 報錯
~~~
4.9 函數必須聲明在頂層
將來 Javascript 的新版本會引入"塊級作用域"。為了與新版本接軌,嚴格模式只允許在全局作用域或函數作用域的頂層聲明函數。也就是說,不允許在非函數的代碼塊內聲明函數。
~~~js
"use strict";
if (true) {
function f() {} // 語法錯誤
}
for (var i = 0; i < 5; i++) {
function f2() {} // 語法錯誤
}
~~~
4.10 保留字
為了向將來 Javascript 的新版本過渡,嚴格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。
使用這些詞作為變量名將會報錯。
~~~js
function package(protected) { // 語法錯誤
"use strict";
var implements; // 語法錯誤
}
~~~
此外,ECMAscript 第五版本身還規定了另一些保留字(class, enum, export, extends, import, super),以及各大瀏覽器自行增加的 const 保留字,也是不能作為變量名的。
[參考](https://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html)
## JS 塊級作用域、變量提升 *******
參考答案:
1.塊級作用域
JS 中作用域有:全局作用域、函數作用域。沒有塊作用域的概念。ECMAScript 6(簡稱 ES6)中新增了塊級作用域。塊作用域由 { } 包括,if 語句和 for 語句里面的{ }也屬于塊作用域。
2.變量提升
* 如果變量聲明在函數里面,則將變量聲明提升到函數的開頭
* 如果變量聲明是一個全局變量,則將變量聲明提升到全局作用域的開頭
解析:
~~~js
< script type = "text/javascript" > {
var a = 1;
console.log(a); // 1
}
console.log(a); // 1
// 可見,通過var定義的變量可以跨塊作用域訪問到。
(function A() {
var b = 2;
console.log(b); // 2
})();
// console.log(b); // 報錯,
// 可見,通過var定義的變量不能跨函數作用域訪問到
if (true) {
var c = 3;
}
console.log(c); // 3
for (var i = 0; i < 4; i++) {
var d = 5;
};
console.log(i); // 4 (循環結束i已經是4,所以此處i為4)
console.log(d); // 5
// if語句和for語句中用var定義的變量可以在外面訪問到,
// 可見,if語句和for語句屬于塊作用域,不屬于函數作用域。
{
var a = 1;
let b = 2;
const c = 3;
{
console.log(a); // 1 子作用域可以訪問到父作用域的變量
console.log(b); // 2 子作用域可以訪問到父作用域的變量
console.log(c); // 3 子作用域可以訪問到父作用域的變量
var aa = 11;
let bb = 22;
const cc = 33;
}
console.log(aa); // 11 // 可以跨塊訪問到子 塊作用域 的變量
// console.log(bb); // 報錯 bb is not defined
// console.log(cc); // 報錯 cc is not defined
} <
/script>
~~~
拓展:
### var、let、const 的區別 *****
* var 定義的變量,沒有塊的概念,可以跨塊訪問, 不能跨函數訪問。
* let 定義的變量,只能在塊作用域里訪問,不能跨塊訪問,也不能跨函數訪問。
* const 用來定義常量,使用時必須初始化(即必須賦值),只能在塊作用域里訪問,而且不能修改。
* 同一個變量只能使用一種方式聲明,不然會報錯
~~~js
< script type = "text/javascript" >
// 塊作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; // 報錯
// let a = 'a'; // 報錯 注:是上面 var a = 1; 那行報錯
// var b = 'b'; // 報錯:本行報錯
// const a = 'a1'; // 報錯 注:是上面 var a = 1; 那行報錯
// let c = 'c'; // 報錯:本行報錯
var aa;
let bb;
// const cc; // 報錯
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined
console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 報錯
// console.log(c); // 報錯
// 函數作用域
(function A() {
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6 (在同一個{ }中,也屬于同一個塊,可以正常訪問到)
console.log(f); // 7 (在同一個{ }中,也屬于同一個塊,可以正常訪問到)
})();
// console.log(d); // 報錯
// console.log(e); // 報錯
// console.log(f); // 報錯
<
/script>
~~~
## null/undefined 的區別 *****
參考答案:
null: Null 類型,代表“空值",代表一個空對象指針,使用 typeof 運算得到 “object",所以你可以認為它是一個特殊的對象值。
undefined: Undefined 類型,當一個聲明了一個變量未初始化時,得到的就是 undefined。
## 重排與重繪的區別,什么情況下會觸發?***
參考答案:
1.簡述重排的概念
瀏覽器下載完頁面中的所有組件(HTML、JavaScript、CSS、圖片)之后會解析生成兩個內部數據結構(DOM 樹和渲染樹),DOM 樹表示頁面結構,渲染樹表示 DOM 節點如何顯示。重排是 DOM 元素的幾何屬性變化,DOM 樹的結構變化,渲染樹需要重新計算。
2.簡述重繪的概念
重繪是一個元素外觀的改變所觸發的瀏覽器行為,例如改變 visibility、outline、背景色等屬性。瀏覽器會根據元素的新屬性重新繪制,使元素呈現新的外觀。由于瀏覽器的流布局,對渲染樹的計算通常只需要遍歷一次就可以完成。但 table 及其內部元素除外,它可能需要多次計算才能確定好其在渲染樹中節點的屬性值,比同等元素要多花兩倍時間,這就是我們盡量避免使用 table 布局頁面的原因之一。
3.簡述重繪和重排的關系
重繪不會引起重排,但重排一定會引起重繪,一個元素的重排通常會帶來一系列的反應,甚至觸發整個文檔的重排和重繪,性能代價是高昂的。
4.什么情況下會觸發重排?
* 頁面渲染初始化時;(這個無法避免)
* 瀏覽器窗口改變尺寸;
* 元素尺寸改變時;
* 元素位置改變時;
* 元素內容改變時;
* 添加或刪除可見的 DOM 元素時。
5.重排優化有如下五種方法
* 將多次改變樣式屬性的操作合并成一次操作,減少 DOM 訪問。
* 如果要批量添加 DOM,可以先讓元素脫離文檔流,操作完后再帶入文檔流,這樣只會觸發一次重排。(fragment 元素的應用)
* 將需要多次重排的元素,position 屬性設為 absolute 或 fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設置為絕對定位。
* 由于 display 屬性為 none 的元素不在渲染樹中,對隱藏的元素操作不會引發其他元素的重排。如果要對一個元素進行復雜的操作時,可以先隱藏它,操作完成后再顯示。這樣只在隱藏和顯示時觸發兩次重排。
* 在內存中多次操作節點,完成后再添加到文檔中去。例如要異步獲取表格數據,渲染到頁面。可以先取得數據后在內存中構建整個表格的 html 片段,再一次性添加到文檔中去,而不是循環添加每一行。
## JavaScript 的數據類型 *****
參考答案:JS 數據類型共有六種,分別是 String、Number、Boolean、Null、Undefined 和 Object 等, 另外,ES6 新增了 Symbol 類型。其中,Object 是引用類型,其他的都是基本類型(Primitive Type)。
## 如何判斷一個對象是否屬于某個類*****
參考答案:instanceof
解析:
~~~js
if (a instanceof Person) {
alert("yes");
}
~~~
## new 操作符具體干了什么呢? ***
參考答案:
樣本一
new 共經過了 4 幾個階段
* 1、創建一個空對象
* 2、設置原型鏈
* 3、讓 Func 中的 this 指向 obj,并執行 Func 的函數體
* 4、判斷 Func 的返回值類型:
樣本二
~~~
function Test(){}
const test = new Test()
~~~
1.創建一個新對象:
~~~
const obj = {}
~~~
2.設置新對象的 constructor 屬性為構造函數的名稱,設置新對象的\_\_proto\_\_屬性指向構造函數的 prototype 對象
~~~
obj.constructor = Test
obj.__proto__ = Test.prototype
~~~
3.使用新對象調用函數,函數中的 this 被指向新實例對象
~~~
Test.call(obj)
~~~
4.將初始化完畢的新對象地址,保存到等號左邊的變量中
## call() 和 apply() 的含義和區別? ****
參考答案:
首先說明兩個方法的含義:
* call:調用一個對象的一個方法,用另一個對象替換當前對象。例如:B.call(A, args1, args2); 即 A 對象調用 B 對象的方法。
* apply:調用一個對象的一個方法,用另一個對象替換當前對象。例如:B.apply(A, arguments); 即 A 對象應用 B 對象的方法。
call 與 apply 的相同點:
* 方法的含義是一樣的,即方法功能是一樣的;
* 第一個參數的作用是一樣的;
call 與 apply 的不同點:兩者傳入的列表形式不一樣
* call 可以傳入多個參數;
* apply 只能傳入兩個參數,所以其第二個參數往往是作為數組形式傳入
想一想哪個性能更好一些呢?
## 解釋 JavaScript 中的作用域與變量聲明提升?*****
參考答案:
* 我對作用域的理解是只會對某個范圍產生作用,而不會對外產生影響的封閉空間。在這樣的一些空間里,外部不能訪問內部變量,但內部可以訪問外部變量。
* 所有申明都會被提升到作用域的最頂上
* 同一個變量申明只進行一次,并且因此其他申明都會被忽略
* 函數聲明的優先級優于變量申明,且函數聲明會連帶定義一起被提升
## bind、call、apply 的區別 ***
參考答案:
call 和 apply 其實是一樣的,區別就在于傳參時參數是一個一個傳或者是以一個數組的方式來傳。
call 和 apply 都是在調用時生效,改變調用者的 this 指向。
let name = 'Jack'
const obj = {name: 'Tom'}
function sayHi() {console.log('Hi! ' + this.name)}
sayHi() // Hi! Jack
sayHi.call(obj) // Hi! Tom
bind 也是改變 this 指向,不過不是在調用時生效,而是返回一個新函數。
const newFunc = sayHi.bind(obj)
newFunc() // Hi! Tom
## 如何獲取瀏覽器版本信息 ***
window.navigator.userAgent
### 作用域的概念及作用
參考答案:
* 作用域 : 起作用的一塊區域
* 作用域的概念: 對變量起保護作用的一塊區域
* 作用: 作用域外部無法獲取到作用域內部聲明的變量,作用域內部能夠獲取到作用域外界聲明的變量。
## 作用域的分類*****
參考答案:塊作用域、詞法作用域、動態作用域
解析:
1 塊作用域 花括號 {}
2 詞法作用域(js 屬于詞法作用域) 作用域只跟在何處被創建有關系,跟在何處被調用沒有關系
3 動態作用域 作用域只跟在何處被調用有關系,跟在何處被創建沒有關系
## js 屬于哪種作用域 ***
參考答案:詞法作用域(函數作用域)
解析:
~~~js
// 塊作用域
/*{
var num =123;
}
console.log(num);*/
// 如果js屬于塊作用域,那么在花括號外部就無法訪問到花括號內部的聲明的num變量。
// 如果js不屬于塊級作用域,那么花括號外部就能夠訪問到花括號內部聲明的num變量
// 能夠輸出num變量,也就說明js不屬于塊級作用。
// 在ES6 之前的版本js是不存在塊級作用域的。
//js屬于詞法作用域還是動態作用域
// js中函數可以幫我們去形成一個作用域
/* function fn(){
var num =123;
}
fn();
//在函數外界能否訪問到num這樣一個變量
console.log(num)*/ //Uncaught ReferenceError: num is not defined
// 如果函數能夠生成一個作用域,那么在函數外界就無法訪問到函數內部聲明的變量。
// js中的函數能夠生成一個作用。 函數作用域 。
// 詞法作用域:作用的外界只跟作用域在何處創建有關系,跟作用域在何處被調用沒有關系
var num = 123;
function f1() {
console.log(num); //
}
function f2() {
var num = 456;
f1(); //f1在f2被調用的時候會被執行 。
}
f2();
//如果js是詞法作用域,那么就會輸出f1被創建的時候外部的num變量 123
//如果js是動態作用域,那么f1執行的時候就會輸出f1被調用時外部環境中的num 456
~~~
## 自執行函數? 用于什么場景?好處?***
參考答案:
自執行函數: 1、聲明一個匿名函數 2、馬上調用這個匿名函數。
作用:創建一個獨立的作用域。
好處:防止變量彌散到全局,以免各種 js 庫沖突。隔離作用域避免污染,或者截斷作用域鏈,避免閉包造成引用變量無法釋放。利用立即執行特性,返回需要的業務函數或對象,避免每次通過條件判斷來處理
場景:一般用于框架、插件等場景
## 多個頁面之間如何進行通信*****
參考答案:有如下幾個方式:
* cookie
* web worker
* localeStorage 和 sessionStorage
## 如何做到修改 url 參數頁面不刷新***
參考答案:
HTML5 引入了`history.pushState()`和`history.replaceState()`方法,它們分別可以添加和修改歷史記錄條目。
~~~js
let stateObj = {
foo: "bar"
};
history.pushState(stateObj, "page 2", "bar.html");
~~~
假設當前頁面為`foo.html`,執行上述代碼后會變為`bar.html`,點擊瀏覽器后退,會變為`foo.html`,但瀏覽器并不會刷新。`pushState()`需要三個參數: 一個狀態對象, 一個標題 (目前被忽略), 和 (可選的) 一個 URL.讓我們來解釋下這三個參數詳細內容:
* 狀態對象 — 狀態對象`state`是一個 JavaScript 對象,通過`pushState ()`創建新的歷史記錄條目。無論什么時候用戶導航到新的狀態,`popstate`事件就會被觸發,且該事件的`state`屬性包含該歷史記錄條目狀態對象的副本。
狀態對象可以是能被序列化的任何東西。原因在于 Firefox 將狀態對象保存在用戶的磁盤上,以便在用戶重啟瀏覽器時使用,我們規定了狀態對象在序列化表示后有 640k 的大小限制。如果你給`pushState()`方法傳了一個序列化后大于 640k 的狀態對象,該方法會拋出異常。如果你需要更大的空間,建議使用`sessionStorage`以及`localStorage`.
* 標題 — Firefox 目前忽略這個參數,但未來可能會用到。傳遞一個空字符串在這里是安全的,而在將來這是不安全的。二選一的話,你可以為跳轉的`state`傳遞一個短標題。
* URL — 該參數定義了新的歷史 URL 記錄。注意,調用`pushState()`后瀏覽器并不會立即加載這個 URL,但可能會在稍后某些情況下加載這個 URL,比如在用戶重新打開瀏覽器時。新 URL 不必須為絕對路徑。如果新 URL 是相對路徑,那么它將被作為相對于當前 URL 處理。新 URL 必須與當前 URL 同源,否則`pushState()`會拋出一個異常。該參數是可選的,缺省為當前 URL。
## 數組方法 pop() push() unshift() shift()****
參考答案:
* arr.pop() 從后面刪除元素,只能是一個,返回值是刪除的元素
* arr.push() 從后面添加元素,返回值為添加完后的數組的長度
* arr.unshift() 從前面添加元素, 返回值是添加完后的數組的長度
* arr.shift() 從前面刪除元素,只能刪除一個 返回值是刪除的元素
## 事件綁定與普通事件有什么區別***
參考答案:
* 用普通事件添加相同事件,下面會覆蓋上面的,而事件綁定不會
* 普通事件是針對非 dom 元素,事件綁定是針對 dom 元素的事件
## 變量提升*****
參考答案:
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87)變量提升
A、js 代碼執行的過程
* 1 變量提升
* 2 代碼從上到下依次執行
var 關鍵字和 function 關鍵字聲明的變量會進行變量提升
B、變量提升發生的環境:發生在代碼所處的當前作用域。
* 變量提升
* 1 var 關鍵字進行的變量提升,會把變量提前聲明,但是不會提前賦值 。
* 2 function 關鍵字對變量進行變量提升,既會把變量提前聲明,又會把變量提前賦值,也就是把整個函數體提升到代碼的頂部
* 3 有一些代碼是不會執行的但是仍舊會發生變量提升, 規則適用于 1, 2
* 3.1 return 之后的代碼依舊會發生變量提升,規則適用于 1,2
* 3.2 代碼報錯之后的代碼依舊會發生變量提升,規則適用于 1,2
* 3.3 break 之后的代碼依舊會發生變量提升,規則適用于 1, 2
* 4 有一些代碼是不會執行但是仍舊會發生變量提升,但是規則要發生變化
* 4.1 if 判斷語句 if 判斷語句中 var 關鍵字以及 function 關鍵字聲明的變量只會發生提前聲明,不會發生提前賦值, 也就是不會吧函數體整體提升到當前作用域頂部。規則跟 1, 2 不適用
* 4.2 switch case 規則跟 1, 2 不適用
* 4.3 do while 規則跟 1, 2 不適用
* 4.4 try catch catch 中聲明的變量只會發生提前聲明,不會發生提前賦值。
* Ps: 在條件判斷語句和 try catch 中的聲明的變量不管是否能夠執行,都只會發生提前
* 聲明,不會發生提前賦值。
解析:
~~~js
// 如果一個變量聲明了但是未賦值,那么輸出這個變量就會輸出 undefined
var num;
console.log(num);
// 如果一個變量沒有聲明也沒有賦值,那么就會報一個錯:
console.log(num); // 輸出一個不存在的變量 Uncaught ReferenceError: num is not defined
~~~
~~~js
// var 關鍵字進行的變量提升
console.log(num);
var num = 123;
console.log(num);
var num = 456;
console.log(num);
// 變量提升之后的代碼:
var num;
console.log(num);
num = 123;
console.log(num);
num = 456;
console.log(num);
~~~
~~~js
// function 關鍵字的變量提升
console.log(fn);
function fn() {
console.log(1);
}
// 變量提升之后的代碼:
function fn() {
console.log(1);
}
console.log(fn); // 輸出fn的函數體
~~~
~~~js
// 3.1 return 之后的代碼依舊會發生變量提升 規則適用于1,2
function fn() {
console.log(num);
return;
var num = 123;
}
fn();
// 變量提升之后的代碼:
function fn() {
var num;
console.log(num);
return;
num = 123;
}
fn(); // undefined
function fn() {
console.log(fo);
return;
function fo() {}
}
fn();
// 變量提升之后的代碼:
function fn() {
function fo() {}
console.log(fo);
return;
}
fn(); //輸出fo的函數體
~~~
~~~js
//3.2 代碼報錯之后的代碼依舊會進行變量提升,規則適用于1,2
console.log(num);
xsasfgdsfqdfsdf; //報一個錯
var num = 123;
console.log(num);
// 變量提升之后的代碼:
var num;
console.log(num); //輸出 undefined
dsagdsqghdwfh; // 報一個錯誤 ,錯誤之后的代碼不會被執行
num = 123;
console.log(num);
~~~
~~~js
//function 關鍵字
console.log(fn);
sasgfdhwhsdqg;
function fn() {}
console.log(fn);
// 變量提升之后的代碼:
function fn() {}
console.log(fn); // 輸出 fn 的函數體
asdgsdgdfgfdg; // 報一個錯誤,報錯之后的代碼不會被執行
console.log(fn);
~~~
~~~js
//4 代碼不執行,但是會進行變量提升,不過規則不適用于1,2
//4.1 if判斷語句
console.log(num);
if (false) {
var num = 123;
}
console.log(num)
// 變量提升之后的代碼:
var num;
console.log(num); //undefined
if (false) {
num = 123;
}
console.log(num) //undefined
console.log(fn);
if (false) {
function fn() {}
}
console.log(fn);
// 變量提升之后的代碼:
var fn;
function fn;
console.log(fn) //undefined
if (false) {
function fn() {}
}
console.log(fn) //undefined
/*function fn//Uncaught SyntaxError: Unexpected end of input*/
~~~
~~~js
// try catch
try {
console.log(num);
} catch (e) {
var num = 123;
}
console.log(num);
var num;
try {
console.log(num); // undefined
} catch (e) {
num = 123;
}
console.log(num); // undefined
try {
console.log(fn);
} catch (e) {
function fn() {}
}
console.log(fn);
var fn;
try {
console.log(fn); // undefined
} catch (e) {
num = 123;
}
console.log(fn); // undefined
~~~
[對應面試題](https://github.com/yisainan/web-interview/blob/master/content/%E7%BC%96%E7%A8%8B%E9%A2%98/%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87.md)
## 如何阻止冒泡與默認行為
參考答案:
* 阻止冒泡行為:非 IE 瀏覽器 stopPropagation(),IE 瀏覽器 window.event.cancelBubble = true
* 阻止默認行為:非 IE 瀏覽器 preventDefault(),IE 瀏覽器 window.event.returnValue = false
解析:
當需要阻止冒泡行為時,可以使用
~~~js
function stopBubble(e) {
//如果提供了事件對象,則這是一個非IE瀏覽器
if (e && e.stopPropagation)
//因此它支持W3C的stopPropagation()方法
e.stopPropagation();
//否則,我們需要使用IE的方式來取消事件冒泡
else window.event.cancelBubble = true;
}
~~~
當需要阻止默認行為時,可以使用
~~~js
//阻止瀏覽器的默認行為
function stopDefault(e) {
//阻止默認瀏覽器動作(W3C)
if (e && e.preventDefault) e.preventDefault();
//IE中阻止函數器默認動作的方式
else window.event.returnValue = false;
return false;
}
~~~
## js 中 this 閉包 作用域 *****
參考答案:
this:指向調用上下文
閉包:定義一個函數就開辟了一個局部作用域,整個 js 執行環境有一個全局作用域
作用域:一個函數可以訪問其他函數中的變量(閉包是一個受保護的變量空間)
~~~js
var f = (function fn() {
var name = 1;
return function() {
name++;
console.log(name)
}
})()
==
>
undefined 有疑問
~~~
## javascript 的同源策略 *****
參考答案:一段腳本只能讀取來自于同一來源的窗口和文檔的屬性
解析:
同源策略:限制從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用于隔離潛在惡意文件的關鍵的安全機制。(來自 MDN 官方的解釋)
簡單來說就是:一段腳本只能讀取來自于同一來源的窗口和文檔的屬性,這里的同一來源指的是主機名、協議和端口號的組合 具體解釋:
(1)源包括三個部分:協議、域名、端口(http 協議的默認端口是 80)。如果有任何一個部分不同,則源不同,那就是跨域了。
(2)限制:這個源的文檔沒有權利去操作另一個源的文檔。這個限制體現在:(要記住)
Cookie、LocalStorage 和 IndexDB 無法獲取。
無法獲取和操作 DOM。
不能發送 Ajax 請求。我們要注意,Ajax 只適合同源的通信。
同源策略帶來的麻煩:ajax 在不同域名下的請求無法實現,需要進行跨域操作
## 事件冒泡與事件捕獲 ***
參考答案:
事件冒泡:由最具體的元素(目標元素)向外傳播到最不具體的元素
事件捕獲:由最不確定的元素到目標元素
## foo = foo||bar ,這行代碼是什么意思?為什么要這樣寫? *****
&& 如果兩個操作數都非零,則條件為真;
|| 如果兩個操作數中有任意一個非零,則條件為真。
[## | 和 ||,& 和 && 的區別](https://www.runoob.com/note/34429)
參考答案:
這種寫法稱為短路表達式
解析:
相當于
var foo;
if (foo) {
foo = foo;
} else {
foo = bar;
}
## javascript 中 this 的指向問題*******
前端 this 指向問題很重要
參考答案:
* 全局環境、普通函數(非嚴格模式)指向 window
* 普通函數(嚴格模式)指向 undefined
* 函數作為對象方法及原型鏈指向的就是上一級的對象
* 構造函數指向構造的對象
* DOM 事件中指向觸發事件的元素
* 箭頭函數...
解析:
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#1%E5%85%A8%E5%B1%80%E7%8E%AF%E5%A2%83)1、全局環境
全局環境下,this 始終指向全局對象(window),無論是否嚴格模式;
~~~js
// 在瀏覽器中,全局對象為 window 對象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
~~~
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#2%E5%87%BD%E6%95%B0%E4%B8%8A%E4%B8%8B%E6%96%87%E8%B0%83%E7%94%A8)2、函數上下文調用
2.1 普通函數
普通函數內部的 this 分兩種情況,嚴格模式和非嚴格模式。
(1)非嚴格模式下,沒有被上一級的對象所調用, this 默認指向全局對象 window。
~~~js
function f1() {
return this;
}
f1() === window; // true
~~~
(2)嚴格模式下,this 指向 undefined。
~~~js
function f2() {
"use strict"; // 這里是嚴格模式
return this;
}
f2() === undefined; // true
~~~
2.2 函數作為對象的方法
(1)函數有被上一級的對象所調用,那么 this 指向的就是上一級的對象。
(2)多層嵌套的對象,內部方法的 this 指向離被調用函數最近的對象(window 也是對象,其內部對象調用方法的 this 指向內部對象, 而非 window)。
~~~js
//方式1
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
//當 o.f()被調用時,函數內的this將綁定到o對象。
console.log(o.f()); // logs 37
//方式2
var o = {
prop: 37
};
function independent() {
return this.prop;
}
//函數f作為o的成員方法調用
o.f = independent;
console.log(o.f()); // logs 37
//方式3
//this 的綁定只受最靠近的成員引用的影響
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // 42
~~~
特殊例子
~~~js
// 例子1
var o = {
a: 10,
b: {
// a:12,
fn: function() {
console.log(this.a); //undefined
console.log(this); //{fn: ?}
}
}
};
o.b.fn();
// 例子2
var o = {
a: 10,
b: {
a: 12,
fn: function() {
console.log(this.a); //undefined
console.log(this); //window
}
}
};
var j = o.b.fn;
j();
// this永遠指向的是最后調用它的對象,也就是看它執行的時候是誰調用的,例子2中雖然函數fn是被對象b所引用,但是在將fn賦值給變量j的時候并沒有執行所以最終指向的是window,這和例子1是不一樣的,例子1是直接執行了fn
~~~
2.3 原型鏈中的 this
(1)如果該方法存在于一個對象的原型鏈上,那么 this 指向的是調用這個方法的對象,就像該方法在對象上一樣。
~~~js
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
~~~
上述例子中,對象 p 沒有屬于它自己的 f 屬性,它的 f 屬性繼承自它的原型。當執行 p.f()時,會查找 p 的原型鏈,找到 f 函數并執行。因為 f 是作為 p 的方法調用的,所以函數中的 this 指向 p。
(2)相同的概念也適用于當函數在一個 getter 或者 setter 中被調用。用作 getter 或 setter 的函數都會把 this 綁定到設置或獲取屬性的對象。
(3)call()和 apply()方法:當函數通過 Function 對象的原型中繼承的方法 call() 和 apply() 方法調用時, 其函數內部的 this 值可綁定到 call() & apply() 方法指定的第一個對象上, 如果第一個參數不是對象,JavaScript 內部會嘗試將其轉換成對象然后指向它。
~~~js
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {
a: 1,
b: 3
};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
function tt() {
console.log(this);
}
// 第一個參數不是對象,JavaScript內部會嘗試將其轉換成對象然后指向它。
tt.call(5); // 內部轉成 Number {[[PrimitiveValue]]: 5}
tt.call("asd"); // 內部轉成 String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}
~~~
(4)bind()方法:由 ES5 引入, 在 Function 的原型鏈上, Function.prototype.bind。通過 bind 方法綁定后, 函數將被永遠綁定在其第一個參數對象上, 而無論其在什么情況下被調用。
~~~js
function f() {
return this.a;
}
var g = f.bind({
a: "azerty"
});
console.log(g()); // azerty
var o = {
a: 37,
f: f,
g: g
};
console.log(o.f(), o.g()); // 37, azerty
~~~
2.4 構造函數中的 this
當一個函數用作構造函數時(使用 new 關鍵字),它的 this 被綁定到正在構造的新對象。
構造器返回的默認值是 this 所指的那個對象,也可以手動返回其他的對象。
~~~js
function C() {
this.a = 37;
}
var o = new C();
console.log(o.a); // 37
// 為什么this會指向o?首先new關鍵字會創建一個空的對象,然后會自動調用一個函數apply方法,將this指向這個空對象,這樣的話函數內部的this就會被這個空的對象替代。
function C2() {
this.a = 37;
return {
a: 38
}; // 手動設置返回{a:38}對象
}
o = new C2();
console.log(o.a); // 38
~~~
特殊例子
當 this 碰到 return 時
~~~js
// 例子1
function fn() {
this.user = "追夢子";
return {};
}
var a = new fn();
console.log(a.user); //undefined
// 例子2
function fn() {
this.user = "追夢子";
return function() {};
}
var a = new fn();
console.log(a.user); //undefined
// 例子3
function fn() {
this.user = "追夢子";
return 1;
}
var a = new fn();
console.log(a.user); //追夢子
// 例子4
function fn() {
this.user = "追夢子";
return undefined;
}
var a = new fn();
console.log(a.user); //追夢子
// 例子5
function fn() {
this.user = "追夢子";
return undefined;
}
var a = new fn();
console.log(a); //fn {user: "追夢子"}
// 例子6
// 雖然null也是對象,但是在這里this還是指向那個函數的實例,因為null比較特殊
function fn() {
this.user = "追夢子";
return null;
}
var a = new fn();
console.log(a.user); //追夢子
// 總結:如果返回值是一個對象,那么this指向的就是那個返回的對象,如果返回值不是一個對象那么this還是指向函數的實例。
~~~
2.5 setTimeout & setInterval
(1)對于延時函數內部的回調函數的 this 指向全局對象 window;
(2)可以通過 bind()方法改變內部函數 this 指向。
~~~js
//默認情況下代碼
function Person() {
this.age = 0;
setTimeout(function() {
console.log(this);
}, 3000);
}
var p = new Person(); //3秒后返回 window 對象
//通過bind綁定
function Person() {
this.age = 0;
setTimeout(
function() {
console.log(this);
}.bind(this),
3000
);
}
var p = new Person(); //3秒后返回構造函數新生成的對象 Person{...}
~~~
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#3%E5%9C%A8-dom-%E4%BA%8B%E4%BB%B6%E4%B8%AD)3、在 DOM 事件中
3.1 作為一個 DOM 事件處理函數
當函數被用作事件處理函數時,它的 this 指向觸發事件的元素(針對 addEventListener 事件)。
~~~js
// 被調用時,將關聯的元素變成藍色
function bluify(e) {
//this指向所點擊元素
console.log("this === e.currentTarget", this === e.currentTarget); // 總是 true
// 當 currentTarget 和 target 是同一個對象時為 true
console.log("this === e.target", this === e.target);
this.style.backgroundColor = "#A5D9F3";
}
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName("*");
// 將bluify作為元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener("click", bluify, false);
}
~~~
3.2 作為一個內聯事件處理函數
(1)當代碼被內聯處理函數調用時,它的 this 指向監聽器所在的 DOM 元素;
(2)當代碼被包括在函數內部執行時,其 this 指向等同于 普通函數直接調用的情況,即在非嚴格模式指向全局對象 window,在嚴格模式指向 undefined:
~~~html
<button onclick="console.log(this)">show me</button>
<button onclick="(function () {console.log(this)})()">show inner this</button>
<button onclick="(function () {'use strict'; console.log(this)})()">
use strict
</button>
~~~
~~~
// 控制臺打印
<button onclick="console.log(this)">show me</button>
Window?{postMessage: ?, blur: ?, focus: ?, close: ?, parent: Window,?…}
undefined
~~~
## [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#4%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0)4、箭頭函數
4.1 全局環境中
在全局代碼中,箭頭函數被設置為全局對象:
~~~js
var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true
~~~
4.2 this 捕獲上下文
箭頭函數沒有自己的 this,而是使用箭頭函數所在的作用域的 this,即指向箭頭函數定義時(而不是運行時)所在的作用域。
~~~js
//1、箭頭函數在函數內部,以非方法的方法使用
function Person() {
this.age = 0;
setInterval(() => {
this.age++;
}, 3000);
}
var p = new Person(); //Person{age: 0}
//普通函數作為內部函數
function Person() {
this.age = 0;
setInterval(function() {
console.log(this);
this.age++;
}, 3000);
}
var p = new Person(); //Window{...}
~~~
4.2 this 捕獲上下文
箭頭函數沒有自己的 this,而是使用箭頭函數所在的作用域的 this,即指向箭頭函數定義時(而不是運行時)所在的作用域。
~~~js
//1、箭頭函數在函數內部,以非方法的方法使用
function Person() {
this.age = 0;
setInterval(() => {
console.log(this);
this.age++;
}, 3000);
}
var p = new Person(); //Person{age: 0}
//普通函數作為內部函數
function Person() {
this.age = 0;
setInterval(function() {
console.log(this);
this.age++;
}, 3000);
}
var p = new Person(); //Window{...}
~~~
在 setTimeout 中的 this 指向了構造函數新生成的對象,而普通函數指向了全局 window 對象。
4.3 箭頭函數作為對象的方法使用
箭頭函數作為對象的方法使用,指向全局 window 對象;而普通函數作為對象的方法使用,則指向調用的對象。
~~~js
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log(this.i, this);
}
};
obj.b(); // undefined window{...}
obj.c(); // 10 Object {...}
~~~
4.4 箭頭函數中,call()、apply()、bind()方法無效
~~~js
var adder = {
base: 1,
//對象的方法內部定義箭頭函數,this是箭頭函數所在的作用域的this,
//而方法add的this指向adder對象,所以箭頭函數的this也指向adder對象。
add: function(a) {
var f = v => v + this.base;
return f(a);
},
//普通函數f1的this指向window
add1: function() {
var f1 = function() {
console.log(this);
};
return f1();
},
addThruCall: function inFun(a) {
var f = v => v + this.base;
var b = {
base: 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 輸出 2
adder.add1(); //輸出全局對象 window{...}
console.log(adder.addThruCall(1)); // 仍然輸出 2(而不是3,其內部的this并沒有因為call() 而改變,其this值仍然為函數inFun的this值,指向對象adder
~~~
4.5 this 指向固定化
箭頭函數可以讓 this 指向固定化,這種特性很有利于封裝回調函數
~~~js
var handler = {
id: "123456",
init: function() {
document.addEventListener(
"click",
event => this.doSomething(event.type),
false
);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
~~~
上面代碼的 init 方法中,使用了箭頭函數,這導致這個箭頭函數里面的 this,總是指向 handler 對象。如果不使用箭頭函數則指向全局 document 對象。
4.6 箭頭函是不適用場景
(1)箭頭函數不適合定義對象的方法(方法內有 this),因為此時指向 window;
(2)需要動態 this 的時候,也不應使用箭頭函數。
~~~js
//例1,this指向定義箭頭函數所在的作用域,它位于對象cat內,但cat不能構成一個作用域,所以指向全局window,改成普通函數后this指向cat對象。
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
};
//例2,此時this也是指向window,不能動態監聽button,改成普通函數后this指向按鈕對象。
var button = document.getElementById("press");
button.addEventListener("click", () => {
this.classList.toggle("on");
});
~~~
## 說說你對作用域鏈的理解 ***
參考答案:作用域鏈的作用是保證執行環境里有權訪問的變量和函數是有序的,作用域鏈的變量只能向上訪問,變量訪問到 window 對象即被終止,作用域鏈向下訪問變量是不被允許的。
## 在 js 中哪些會被隱式轉換為 false*****
參考答案:Undefined、null、關鍵字 false、NaN、零、空字符串
## 列舉瀏覽器對象模型 BOM 里常用的至少 4 個對象,并列舉 window 對象的常用方法至少 5 個?
參考答案:
對象:Window,document,location,screen,history,navigator。 方法:Alert(),confirm(),prompt(),open(),close()。
## 對象淺拷貝和深拷貝有什么區別 *******
參考答案:在`JS`中,除了基本數據類型,還存在對象、數組這種引用類型。 基本數據類型,拷貝是直接拷貝變量的值,而引用類型拷貝的其實是變量的地址。
~~~
let o1 = {a: 1}
let o2 = o1
~~~
在這種情況下,如果改變`o1`或`o2`其中一個值的話,另一個也會變,因為它們都指向同一個地址。
~~~
o2.a = 3
console.log(o1.a) // 3
~~~
而淺拷貝和深拷貝就是在這個基礎之上做的區分,如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有重新創建一個新的對象,則認為是淺拷貝。反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,并且復制其內的成員變量,則認為是深拷貝。
## 如何編寫高性能的 Javascript? *****
這個在日常開發中去注意
參考答案:
* 使用 DocumentFragment 優化多次 append
* 通過模板元素 clone ,替代 createElement
* 使用一次 innerHTML 賦值代替構建 dom 元素
* 使用 firstChild 和 nextSibling 代替 childNodes 遍歷 dom 元素
* 使用 Array 做為 StringBuffer ,代替字符串拼接的操作
* 將循環控制量保存到局部變量
* 順序無關的遍歷時,用 while 替代 for
* 將條件分支,按可能性順序從高到低排列
* 在同一條件子的多( >2 )條件分支時,使用 switch 優于 if
* 使用三目運算符替代條件分支
* 需要不斷執行的時候,優先考慮使用 setInterval
## 使用 let、var 和 const 創建變量有什么區別 *******
這個涉及變量提升, 還是挺重要的
參考答案:
用 var 聲明的變量的作用域是它當前的執行上下文,它可以是嵌套的函數,也可以是聲明在任何函數外的變量。let 和 const 是塊級作用域,意味著它們只能在最近的一組花括號(function、if-else 代碼塊或 for 循環中)中訪問。
~~~js
function foo() {
// 所有變量在函數中都可訪問
var bar = "bar";
let baz = "baz";
const qux = "qux";
console.log(bar); // bar
console.log(baz); // baz
console.log(qux); // qux
}
console.log(bar); // ReferenceError: bar is not defined
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
~~~
~~~js
if (true) {
var bar = "bar";
let baz = "baz";
const qux = "qux";
}
// 用 var 聲明的變量在函數作用域上都可訪問
console.log(bar); // bar
// let 和 const 定義的變量在它們被定義的語句塊之外不可訪問
console.log(baz); // ReferenceError: baz is not defined
console.log(qux); // ReferenceError: qux is not defined
~~~
var 會使變量提升,這意味著變量可以在聲明之前使用。let 和 const 不會使變量提升,提前使用會報錯。
~~~js
console.log(foo); // undefined
var foo = "foo";
console.log(baz); // ReferenceError: can't access lexical declaration 'baz' before initialization
let baz = "baz";
console.log(bar); // ReferenceError: can't access lexical declaration 'bar' before initialization
const bar = "bar";
~~~
用 var 重復聲明不會報錯,但 let 和 const 會。
~~~js
var foo = "foo";
var foo = "bar";
console.log(foo); // "bar"
let baz = "baz";
let baz = "qux"; // Uncaught SyntaxError: Identifier 'baz' has already been declared
~~~
let 和 const 的區別在于:let 允許多次賦值,而 const 只允許一次。
~~~js
// 這樣不會報錯。
let foo = "foo";
foo = "bar";
// 這樣會報錯。
const baz = "baz";
baz = "qux";
~~~
解析:[參考](https://github.com/yangshun/front-end-interview-handbook/blob/master/Translations/Chinese/questions/javascript-questions.md#%E4%BD%BF%E7%94%A8letvar%E5%92%8Cconst%E5%88%9B%E5%BB%BA%E5%8F%98%E9%87%8F%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB)
## prototype 和\_\_proto\_\_的關系是什么*****
參考答案:
所有的對象都擁有\_\_proto\_\_屬性,它指向對象構造函數的 prototype 屬性
~~~
let obj = {}
obj.__proto__ === Object.prototype // true
function Test(){}
var test = new Test()
test.__proto__ == Test.prototype // true
~~~
所有的函數都同時擁有\_\_proto\_\_和 protytpe 屬性 函數的\_\_proto\_\_指向自己的函數實現 函數的 protytpe 是一個對象 所以函數的 prototype 也有\_\_proto\_\_屬性 指向 Object.prototype
~~~
function func() {}
func.prototype.__proto__ === Object.prototype // true
~~~
Object.prototype.\_\_proto\_\_指向 null
~~~
Object.prototype.__proto__ // null
~~~
## 如何避免重繪或者重排?***
參考答案:
1.分離讀寫操作
~~~
var curLeft=div.offsetLeft;
var curTop=div.offsetTop;
div.style.left=curLeft+1+'px';
div.style.top=curTop+1+'px';
~~~
2.樣式集中改變
~~~
可以添加一個類,樣式都在類中改變
~~~
3.可以使用absolute脫離文檔流。
4.使用 display:none ,不使用 visibility,也不要改變 它的 z-index
5.能用css3實現的就用css3實現。
## forEach,map和filter的區別(嗶哩嗶哩)****
參考答案:
* filter函數,顧名思義,它是一個用來過濾的函數。他可以通過指定的過濾條件,刪選出數組中符合條件的元素,并返回。
* map函數,這個函數與filter函數不同之處在于,filter()把傳入的函數依次作用于每個元素,然后根據返回值是true還是false決定保留還是丟棄該元素。而map則會返回傳入函數return的值。
* forEach函數,可以實現對數組的遍歷,和map函數與filter函數不同的是它沒有返回值。
## delete 數組的 item,數組的 length 是否會 -1 *****
參考答案:不會
解析:
### [](https://github.com/yisainan/web-interview/edit/master/content/js/js.md#delete-arrayindex)delete Array\[index\]
~~~js
const arr = ['a', 'b', 'c', 'd', 'e'];
let result = delete arr[1];
console.log(result); // true;
console.log(arr); // ['a', undefined, 'c', 'd', 'e']
console.log(arr.length); // 5
console.log(arr[1]); // undefined
~~~
使用delete刪除元素,返回true和false, true表示刪除成功,false表示刪除失敗。使用delete刪除數組元素并不會改變原數組的長度,只是把被刪除元素的值變為undefined。
## 執行上下文****
參考答案:
執行上下文可以簡單理解為一個對象:
它包含三個部分:
* 變量對象(VO)
* 作用域鏈(詞法作用域)
* this指向
它的類型:
* 全局執行上下文
* 函數執行上下文
* eval執行上下文
代碼執行過程:
* 創建 全局上下文 (global EC)
* 全局執行上下文 (caller) 逐行 自上而下 執行。遇到函數時,函數執行上下文 (callee) 被push到執行棧頂層
* 函數執行上下文被激活,成為 active EC, 開始執行函數中的代碼,caller 被掛起
* 函數執行完后,callee 被pop移除出執行棧,控制權交還全局上下文 (caller),繼續執行
## 怎樣理解setTimeout 執行誤差****
參考答案:定時器是屬于 宏任務(macrotask) 。如果當前 執行棧 所花費的時間大于 定時器 時間,那么定時器的回調在 宏任務(macrotask) 里,來不及去調用,所有這個時間會有誤差。
解析:[參考](https://juejin.im/post/5cfc9d266fb9a07edb3939ea?utm_medium=hao.caibaojian.com&utm_source=hao.caibaojian.com)
## 為什么for循環嵌套順序會影響性能?***
參考答案:把循環次數大的放在內層,執行時間會比較短
~~~js
var t1 = new Date().getTime()
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 10000; k++) {}
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
~~~
| 變量 | 實例化(次數) | 初始化(次數) | 比較(次數) | 自增(次數) |
| --- | --- | --- | --- | --- |
| i | 1 | 1 | 10 | 10 |
| j | 10 | 10 | 10 \* 100 | 10 \* 100 |
| k | 10 \* 100 | 10 \* 100 | 10 \* 100 \* 1000 | 10 \* 100 \* 1000 |
~~~js
for (let i = 0; i < 10000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 100; k++) {
}
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
~~~
| 變量 | 實例化(次數) | 初始化(次數) | 比較(次數) | 自增(次數) |
| --- | --- | --- | --- | --- |
| i | 1 | 1 | 1000 | 1000 |
| j | 1000 | 1000 | 1000 \* 100 | 1000 \* 100 |
| k | 1000 \* 100 | 1000 \* 100 | 1000 \* 100 \* 10 | 1000 \* 100 \* 10 |
解析:[參考](https://blog.csdn.net/weixin_42182143/article/details/98682537)
## typeof 與 instanceof 區別*****
~~~
1、typeof返回結果是該類型的字符串形式表示【6】(number、string、undefined、boolean、function、object)
2、instanceof是用來判斷 A 是否為 B 的實例,表達式為:A instanceof B,如果 A 是 B 的實例,則返回 true,否則返回 false。 在這里需要特別注意的是:instanceof 檢測的是原型。
~~~
## 微任務和宏任務****
參考答案:
~~~js
/*
* 宏任務
* 分類: setTimeout setInterval requrestAnimationFrame
* 1.宏任務所處的隊列就是宏任務隊列
* 2.第一個宏任務隊列中只有一個任務: 執行主線程的js代碼
* 3.宏任務隊列可以有多個
* 4.當宏任務隊列的中的任務全部執行完以后會查看是否有微任務隊列如果有先執行微任務隊列中的所有任務,如果沒有就查看是否有宏任務隊列
*
* 微任務
* 分類: new Promise().then(回調) process.nextTick
* 1.微任務所處的隊列就是微任務隊列
* 2.只有一個微任務隊列
* 3.在上一個宏任務隊列執行完畢后如果有微任務隊列就會執行微任務隊列中的所有任務
* */
console.log('----------------- start -----------------');
setTimeout(() => {
console.log('setTimeout');
}, 0)
new Promise((resolve, reject) => {
for (var i = 0; i < 5; i++) {
console.log(i);
}
resolve(); // 修改promise實例對象的狀態為成功的狀態
}).then(() => {
console.log('promise實例成功回調執行');
})
console.log('----------------- end -----------------');
~~~
## JavaScript 中 undefined 和 not defined 的區別 ****
參考答案:undefined是沒有初始化,not defined是沒有聲明
## JavaScript怎么清空數組?*****
參考答案:
方法1
arrayList = \[\];
直接改變arrayList所指向的對象,原對象并不改變。
方法2
arrayList.length = 0;
這種方法通過設置length=0 使原數組清除元素。
方法3
arrayList.splice(0, arrayList.length);
## 兩種函數聲明有什么區別?****
~~~js
var foo = function() {
// Some code
};
function bar() {
// Some code
};
~~~
參考答案:
foo的定義是在運行時。想系統說明這個問題,我們要引入變量提升的這一概念。
我們可以運行下如下代碼看看結果。
~~~js
console.log(foo)
console.log(bar)
var foo = function() {
// Some code
};
function bar() {
// Some code
};
~~~
輸出為
~~~
undefined
function bar(){
// Some code
};
~~~
為什么那?為什么 foo 打印出來是 undefined,而 bar打印出來卻是函數?
JavaScript在執行時,會將變量提升。
所以上面代碼JavaScript 引擎在實際執行時按這個順序執行。
~~~js
// foo bar的定義位置被提升
function bar() {
// Some code
};
var foo;
console.log(foo)
console.log(bar)
foo = function() {
// Some code
};
~~~
## 什么是跨域?跨域請求資源的方法有哪些?***
跨域一般我都是在后臺搞, 前端部署到Nginx, 請求轉發給后臺, 或者 后臺設置 cors. jsonp 基本不用了, 這個印象中只能get請求
參考答案:
~~~
(1)、porxy代理
定義和用法:proxy代理用于將請求發送給后臺服務器,通過服務器來發送請求,然后將請求的結果傳遞給前端。
實現方法:通過nginx代理;
注意點:1、如果你代理的是https協議的請求,那么你的proxy首先需要信任該證書(尤其是自定義證書)或者忽略證書檢查,否則你的請求無法成功。
(2)、CORS 【Cross-Origin Resource Sharing】
定義和用法:是現代瀏覽器支持跨域資源請求的一種最常用的方式。
使用方法:一般需要后端人員在處理請求數據的時候,添加允許跨域的相關操作。如下:
res.writeHead(200, {
"Content-Type": "text/html; charset=UTF-8",
"Access-Control-Allow-Origin":'http://localhost',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type'
});
(3)、jsonp
定義和用法:通過動態插入一個script標簽。瀏覽器對script的資源引用沒有同源限制,同時資源加載到頁面后會立即執行(沒有阻塞的情況下)。
特點:通過情況下,通過動態創建script來讀取他域的動態資源,獲取的數據一般為json格式。
實例如下:
<script>
function testjsonp(data) {
console.log(data.name); // 獲取返回的結果
}
</script>
<script>
var _script = document.createElement('script');
_script.type = "text/javascript";
_script.src = "http://localhost:8888/jsonp?callback=testjsonp";
document.head.appendChild(_script);
</script>
缺點:
1、這種方式無法發送post請求(這里)
2、另外要確定jsonp的請求是否失敗并不容易,大多數框架的實現都是結合超時時間來判定。
~~~
## null 和 undefined 的區別?****
參考答案:
~~~
首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。
undefined 代表的含義是未定義,null 代表的含義是空對象。一般變量聲明了但還沒有定義的時候會返回 undefined,null
主要用于賦值給一些可能會返回對象的變量,作為初始化。
undefined 在 js 中不是一個保留字,這意味著我們可以使用 undefined 來作為一個變量名,這樣的做法是非常危險的,它
會影響我們對 undefined 值的判斷。但是我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。
當我們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回 “object”,這是一個歷史遺留的問題。當我們使用雙等
號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。
~~~
詳細資料可以參考:[《JavaScript 深入理解之 undefined 與 null》](http://cavszhouyou.top/JavaScript%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E4%B9%8Bundefined%E4%B8%8Enull.html)
## JavaScript 原型,原型鏈? 有什么特點?***
參考答案:
~~~
在 js 中我們是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性值,這個屬性值是一個對
象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當我們使用構造函數新建一個對象后,在這個對象的內部
將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說我們
是不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 __proto__ 屬性來讓我們訪問這個屬性,但是我們最好不要使用這
個屬性,因為它不是規范中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,我們可以通過這個方法來獲取對
象的原型。
當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又
會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就
是我們新建的對象為什么能夠使用 toString() 等方法的原因。
特點:
JavaScript 對象是通過引用來傳遞的,我們創建的每個新對象實體中并沒有一份屬于自己的原型副本。當我們修改原型時,與
之相關的對象也會繼承這一改變。
~~~
詳細資料可以參考:[《JavaScript 深入理解之原型與原型鏈》](http://cavszhouyou.top/JavaScript%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E4%B9%8B%E5%8E%9F%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE.html)
## isNaN 和 Number.isNaN 函數的區別?****
參考答案:
~~~
函數 isNaN 接收參數后,會嘗試將這個參數轉換為數值,任何不能被轉換為數值的的值都會返回 true,因此非數字值傳入也會
返回 true ,會影響 NaN 的判斷。
函數 Number.isNaN 會首先判斷傳入參數是否為數字,如果是數字再繼續判斷是否為 NaN ,這種方法對于 NaN 的判斷更為
準確。
~~~
## 其他值到字符串的轉換規則?****
參考答案:
~~~
規范的 9.8 節中定義了抽象操作 ToString ,它負責處理非字符串到字符串的強制類型轉換。
(1)Null 和 Undefined 類型 ,null 轉換為 "null",undefined 轉換為 "undefined",
(2)Boolean 類型,true 轉換為 "true",false 轉換為 "false"。
(3)Number 類型的值直接轉換,不過那些極小和極大的數字會使用指數形式。
(4)Symbol 類型的值直接轉換,但是只允許顯式強制類型轉換,使用隱式強制類型轉換會產生錯誤。
(5)對普通對象來說,除非自行定義 toString() 方法,否則會調用 toString()(Object.prototype.toString())
來返回內部屬性 [[Class]] 的值,如"[object Object]"。如果對象有自己的 toString() 方法,字符串化時就會
調用該方法并使用其返回值。
~~~
## 其他值到數字值的轉換規則?****
參考答案:
~~~
有時我們需要將非數字值當作數字來使用,比如數學運算。為此 ES5 規范在 9.3 節定義了抽象操作 ToNumber。
(1)Undefined 類型的值轉換為 NaN。
(2)Null 類型的值轉換為 0。
(3)Boolean 類型的值,true 轉換為 1,false 轉換為 0。
(4)String 類型的值轉換如同使用 Number() 函數進行轉換,如果包含非數字值則轉換為 NaN,空字符串為 0。
(5)Symbol 類型的值不能轉換為數字,會報錯。
(6)對象(包括數組)會首先被轉換為相應的基本類型值,如果返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換為數字。
為了將值轉換為相應的基本類型值,抽象操作 ToPrimitive 會首先(通過內部操作 DefaultValue)檢查該值是否有valueOf() 方法。如果有并且返回基本類型值,就使用該值進行強制類型轉換。如果沒有就使用 toString() 的返回值(如果存在)來進行強制類型轉換。
如果 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
~~~
## 其他值到布爾類型的值的轉換規則?****
參考答案:
~~~
ES5 規范 9.2 節中定義了抽象操作 ToBoolean,列舉了布爾強制類型轉換所有可能出現的結果。
以下這些是假值:
? undefined
? null
? false
? +0、-0 和 NaN
? ""
假值的布爾強制類型轉換結果為 false。從邏輯上說,假值列表以外的都應該是真值。
~~~
## js 類型轉換******

## == vs === 有什么區別? ******
對于`==`來說,如果對比雙方的類型**不一樣**的話,就會進行**類型轉換**,這也就用到了我們上一章節講的內容。
假如我們需要對比`x`和`y`是否相同,就會進行如下判斷流程:
1. 首先會判斷兩者類型是否**相同**。相同的話就是比大小了
2. 類型不相同的話,那么就會進行類型轉換
3. 會先判斷是否在對比`null`和`undefined`,是的話就會返回`true`
4. 判斷兩者類型是否為`string`和`number`,是的話就會將字符串轉換為`number`
~~~js
1 == '1'
↓
1 == 1
~~~
5. 判斷其中一方是否為`boolean`,是的話就會把`boolean`轉為`number`再進行判斷
~~~js
'1' == true
↓
'1' == 1
↓
1 == 1
~~~
6. 判斷其中一方是否為`object`且另一方為`string`、`number`或者`symbol`,是的話就會把`object`轉為原始類型再進行判斷
~~~js
'1' == { name: 'yck' }
↓
'1' == '[object Object]'
~~~
~~~!
思考題:看完了上面的步驟,對于 [] == ![] 你是否能正確寫出答案呢?
~~~
如果你覺得記憶步驟太麻煩的話,我還提供了流程圖供大家使用:

當然了,這個流程圖并沒有將所有的情況都列舉出來,我這里只將常用到的情況列舉了,如果你想了解更多的內容可以參考[標準文檔](https://262.ecma-international.org/5.1/#sec-11.9.1)。
對于`===`來說就簡單多了,就是判斷兩者類型和值是否相同。
更多的對比可以閱讀這篇[文章](https://link.juejin.cn/?target=https%3A%2F%2Ffelix-kling.de%2Fjs-loose-comparison%2F "https://felix-kling.de/js-loose-comparison/")
## || 和 && 操作符的返回值?*****
參考答案:
|| 和 && 首先會對第一個操作數執行條件判斷,如果其不是布爾值就先進行 ToBoolean 強制類型轉換,然后再執行條件
判斷。
對于 || 來說,如果條件判斷結果為 true 就返回第一個操作數的值,如果為 false 就返回第二個操作數的值。
&& 則相反,如果條件判斷結果為 true 就返回第二個操作數的值,如果為 false 就返回第一個操作數的值。
|| 和 && 返回它們其中一個操作數的值,而非條件判斷的結果
## 談談 This 對象的理解。*****
參考答案:
this 是執行上下文中的一個屬性,它指向最后一次調用這個方法的對象。在實際開發中,this 的指向可以通過四種調用模
式來判斷。
1.第一種是函數調用模式,當一個函數不是一個對象的屬性時,直接作為函數來調用時,this 指向全局對象。
2.第二種是方法調用模式,如果一個函數作為一個對象的方法來調用時,this 指向這個對象。
3.第三種是構造器調用模式,如果一個函數用 new 調用時,函數執行前會新創建一個對象,this 指向這個新創建的對象。
4.第四種是 apply 、 call 和 bind 調用模式,這三個方法都可以顯示的指定調用函數的 this 指向。其中 apply 方法接收兩個參數:一個是 this 綁定的對象,一個是參數數組。call 方法接收的參數,第一個是 this 綁定的對象,后面的其余參數是傳入函數執行的參數。也就是說,在使用 call() 方法時,傳遞給函數的參數必須逐個列舉出來。bind 方法通過傳入一個對象,返回一個 this 綁定了傳入對象的新函數。這個函數的 this 指向除了使用 new 時會被改變,其他情況下都不會改變。
這四種方式,使用構造器調用模式的優先級最高,然后是 apply 、 call 和 bind 調用模式,然后是方法調用模式,然后
是函數調用模式。
《JavaScript 深入理解之 this 詳解》
## javascript 代碼中的 "use strict"; 是什么意思 ? 使用它區別是什么?****
參考答案:
相關知識點:
~~~
use strict 是一種 ECMAscript5 添加的(嚴格)運行模式,這種模式使得 Javascript 在更嚴格的條件下運行。
設立"嚴格模式"的目的,主要有以下幾個:
~~~
* 消除 Javascript 語法的一些不合理、不嚴謹之處,減少一些怪異行為;
* 消除代碼運行的一些不安全之處,保證代碼運行的安全;
* 提高編譯器效率,增加運行速度;
* 為未來新版本的 Javascript 做好鋪墊。
區別:
* 1.禁止使用 with 語句。
* 2.禁止 this 關鍵字指向全局對象。
* 3.對象不能有重名的屬性。
回答:
~~~
use strict 指的是嚴格運行模式,在這種模式對 js 的使用添加了一些限制。比如說禁止 this 指向全局對象,還有禁止使
用 with 語句等。設立嚴格模式的目的,主要是為了消除代碼使用中的一些不安全的使用方式,也是為了消除 js 語法本身的一
些不合理的地方,以此來減少一些運行時的怪異的行為。同時使用嚴格運行模式也能夠提高編譯的效率,從而提高代碼的運行速度。
我認為嚴格模式代表了 js 一種更合理、更安全、更嚴謹的發展方向。
~~~
詳細資料可以參考:[《Javascript 嚴格模式詳解》](http://www.ruanyifeng.com/blog/2013/01/javascript_strict_mode.html)
## instanceof 的作用?****
參考答案:
~~~js
// instanceof 運算符用于判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。
// 實現:
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 獲取對象的原型
prototype = right.prototype; // 獲取構造函數的 prototype 對象
// 判斷構造函數的 prototype 對象是否在對象的原型鏈上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
~~~
詳細資料可以參考:[《instanceof》](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof)
## 談一談瀏覽器的緩存機制?****
參考答案:
~~~
瀏覽器的緩存機制指的是通過在一段時間內保留已接收到的 web 資源的一個副本,如果在資源的有效時間內,發起了對這個資源的再一次請求,那么瀏覽器會直接使用緩存的副本,而不是向服務器發起請求。使用 web 緩存可以有效地提高頁面的打開速度,減少不必要的網絡帶寬的消耗。
web 資源的緩存策略一般由服務器來指定,可以分為兩種,分別是強緩存策略和協商緩存策略。
使用強緩存策略時,如果緩存資源有效,則直接使用緩存資源,不必再向服務器發起請求。強緩存策略可以通過兩種方式來設置,分別是 http 頭信息中的 Expires 屬性和 Cache-Control 屬性。
服務器通過在響應頭中添加 Expires 屬性,來指定資源的過期時間。在過期時間以內,該資源可以被緩存使用,不必再向服務器發送請求。這個時間是一個絕對時間,它是服務器的時間,因此可能存在這樣的問題,就是客戶端的時間和服務器端的時間不一致,或者用戶可以對客戶端時間進行修改的情況,這樣就可能會影響緩存命中的結果。
Expires 是 http1.0 中的方式,因為它的一些缺點,在 http 1.1 中提出了一個新的頭部屬性就是 Cache-Control 屬性,
它提供了對資源的緩存的更精確的控制。它有很多不同的值,常用的比如我們可以通過設置 max-age 來指定資源能夠被緩存的時間
的大小,這是一個相對的時間,它會根據這個時間的大小和資源第一次請求時的時間來計算出資源過期的時間,因此相對于 Expires
來說,這種方式更加有效一些。常用的還有比如 private ,用來規定資源只能被客戶端緩存,不能夠代理服務器所緩存。還有如 n
o-store ,用來指定資源不能夠被緩存,no-cache 代表該資源能夠被緩存,但是立即失效,每次都需要向服務器發起請求。
一般來說只需要設置其中一種方式就可以實現強緩存策略,當兩種方式一起使用時,Cache-Control 的優先級要高于 Expires 。
使用協商緩存策略時,會先向服務器發送一個請求,如果資源沒有發生修改,則返回一個 304 狀態,讓瀏覽器使用本地的緩存副本。
如果資源發生了修改,則返回修改后的資源。協商緩存也可以通過兩種方式來設置,分別是 http 頭信息中的 Etag 和 Last-Modified 屬性。
服務器通過在響應頭中添加 Last-Modified 屬性來指出資源最后一次修改的時間,當瀏覽器下一次發起請求時,會在請求頭中添加一個 If-Modified-Since 的屬性,屬性值為上一次資源返回時的 Last-Modified 的值。當請求發送到服務器后服務器會通過這個屬性來和資源的最后一次的修改時間來進行比較,以此來判斷資源是否做了修改。如果資源沒有修改,那么返回 304 狀態,讓客戶端使用本地的緩存。如果資源已經被修改了,則返回修改后的資源。使用這種方法有一個缺點,就是 Last-Modified 標注的最后修改時間只能精確到秒級,如果某些文件在1秒鐘以內,被修改多次的話,那么文件已將改變了但是 Last-Modified 卻沒有改變,
這樣會造成緩存命中的不準確。
因為 Last-Modified 的這種可能發生的不準確性,http 中提供了另外一種方式,那就是 Etag 屬性。服務器在返回資源的時候,在頭信息中添加了 Etag 屬性,這個屬性是資源生成的唯一標識符,當資源發生改變的時候,這個值也會發生改變。在下一次資源請求時,瀏覽器會在請求頭中添加一個 If-None-Match 屬性,這個屬性的值就是上次返回的資源的 Etag 的值。服務接收到請求后會根據這個值來和資源當前的 Etag 的值來進行比較,以此來判斷資源是否發生改變,是否需要返回資源。通過這種方式,比 Last-Modified 的方式更加精確。
當 Last-Modified 和 Etag 屬性同時出現的時候,Etag 的優先級更高。使用協商緩存的時候,服務器需要考慮負載平衡的問題,因此多個服務器上資源的 Last-Modified 應該保持一致,因為每個服務器上 Etag 的值都不一樣,因此在考慮負載平衡時,最好不要設置 Etag 屬性。
強緩存策略和協商緩存策略在緩存命中時都會直接使用本地的緩存副本,區別只在于協商緩存會向服務器發送一次請求。它們緩存不命中時,都會向服務器發送請求來獲取資源。在實際的緩存機制中,強緩存策略和協商緩存策略是一起合作使用的。瀏覽器首先會根據請求的信息判斷,強緩存是否命中,如果命中則直接使用資源。如果不命中則根據頭信息向服務器發起請求,使用協商緩存,如果協商緩存命中的話,則服務器不返回資源,瀏覽器直接使用本地資源的副本,如果協商緩存不命中,則瀏覽器返回最新的資源給瀏覽器。
~~~
詳細資料可以參考:[《淺談瀏覽器緩存》](https://segmentfault.com/a/1190000012573337)[《前端優化:瀏覽器緩存技術介紹》](https://juejin.im/post/5b9346dcf265da0aac6fbe57#heading-3)[《請求頭中的 Cache-Control》](https://www.web-tinker.com/article/21221.html)[《Cache-Control 字段值詳解》](https://juejin.im/post/5c2d6c9ae51d450cf4195a08)
## 簡單談一下 cookie ?****
參考答案:
~~~
我的理解是 cookie 是服務器提供的一種用于維護會話狀態信息的數據,通過服務器發送到瀏覽器,瀏覽器保存在本地,當下一次有同源的請求時,將保存的 cookie 值添加到請求頭部,發送給服務端。這可以用來實現記錄用戶登錄狀態等功能。cookie 一般可以存儲 4k 大小的數據,并且只能夠被同源的網頁所共享訪問。
服務器端可以使用 Set-Cookie 的響應頭部來配置 cookie 信息。一條cookie 包括了9個屬性值 name、value、expires、domain、path、secure、HttpOnly、SameSite、Priority。其中 name 和 value 分別是 cookie 的名字和值。expires 指定了 cookie 失效的時間,domain 是域名、path是路徑,domain 和 path 一起限制了 cookie 能夠被哪些 url 訪問。secure 規定了 cookie 只能在確保安全的情況下傳輸,HttpOnly 規定了這個 cookie 只能被服務器訪問,不能使用 js 腳本訪問。SameSite 屬性用來限制第三方 cookie,可以有效防止 CSRF 攻擊,從而減少安全風險。Priority 是 chrome 的提案,定義了三種優先級,當 cookie 數量超出時低優先級的 cookie 會被優先清除。
在發生 xhr 的跨域請求的時候,即使是同源下的 cookie,也不會被自動添加到請求頭部,除非顯示地規定。
~~~
詳細資料可以參考:[《HTTP cookies》](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies)[《聊一聊 cookie》](https://segmentfault.com/a/1190000004556040)
## 模塊化開發怎么做?****
參考答案:
~~~
我對模塊的理解是,一個模塊是實現一個特定功能的一組方法。在最開始的時候,js 只實現一些簡單的功能,所以并沒有模塊的概念
,但隨著程序越來越復雜,代碼的模塊化開發變得越來越重要。
由于函數具有獨立作用域的特點,最原始的寫法是使用函數來作為模塊,幾個函數作為一個模塊,但是這種方式容易造成全局變量的污
染,并且模塊間沒有聯系。
后面提出了對象寫法,通過將函數作為一個對象的方法來實現,這樣解決了直接使用函數作為模塊的一些缺點,但是這種辦法會暴露所
有的所有的模塊成員,外部代碼可以修改內部屬性的值。
現在最常用的是立即執行函數的寫法,通過利用閉包來實現模塊私有作用域的建立,同時不會對全局作用域造成污染。
~~~
詳細資料可以參考:[《淺談模塊化開發》](https://juejin.im/post/5ab378c46fb9a028ce7b824f)[《Javascript 模塊化編程(一):模塊的寫法》](http://www.ruanyifeng.com/blog/2012/10/javascript_module.html)[《前端模塊化:CommonJS,AMD,CMD,ES6》](https://juejin.im/post/5aaa37c8f265da23945f365c)[《Module 的語法》](http://es6.ruanyifeng.com/#docs/module)
## js 的幾種模塊規范?****
參考答案:
~~~
js 中現在比較成熟的有四種模塊加載方案。
第一種是 CommonJS 方案,它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。這種模塊加載方案是
服務器端的解決方案,它是以同步的方式來引入模塊的,因為在服務端文件都存儲在本地磁盤,所以讀取非常快,所以以同步的方式
加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網絡請求,因此使用異步加載的方式更加合適。
第二種是 AMD 方案,這種方案采用異步加載的方式來加載模塊,模塊的加載不影響后面語句的執行,所有依賴這個模塊的語句都定
義在一個回調函數里,等到加載完成后再執行回調函數。require.js 實現了 AMD 規范。
第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問題,sea.js 實現了 CMD 規范。它和 require.js
的區別在于模塊定義時對依賴的處理不同和對依賴模塊的執行時機的處理不同。參考60
第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導入導出模塊。這種方案和上面三種方案都不同。參考 61。
~~~
## 如何判斷當前腳本運行在瀏覽器還是 node 環境中?(阿里)**
參考答案:
~~~
typeof window === 'undefined' ? 'node' : 'browser';
通過判斷當前環境的 window 對象類型是否為 undefined,如果是undefined,則說明當前腳本運行在node環境,否則說明運行在window環境。
~~~
## js 的事件循環是什么?****
參考答案:
相關知識點:
~~~
事件隊列是一個存儲著待執行任務的隊列,其中的任務嚴格按照時間先后順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最后執行。事件隊列每次僅執行一個任務,在該任務執行完畢之后,再執行下一個任務。執行棧則是一個類似于函數調用棧的運行容器,當執行棧為空時,JS 引擎便檢查事件隊列,如果不為空的話,事件隊列便將第一個任務壓入執行棧中運行。
~~~
回答:
~~~
因為 js 是單線程運行的,在代碼執行的時候,通過將不同函數的執行上下文壓入執行棧中來保證代碼的有序執行。在執行同步代碼的時候,如果遇到了異步事件,js 引擎并不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其他任務。當異步事件執行完畢后,再將異步事件對應的回調加入到與當前執行棧中不同的另一個任務隊列中等待執行。任務隊列可以分為宏任務對列和微任務對列,當當前執行棧中的事件執行完畢后,js 引擎首先會判斷微任務對列中是否有任務可以執行,如果有就將微任務隊首的事件壓入棧中執行。當微任務對列中的任務都執行完成后再去判斷宏任務對列中的任務。
微任務包括了 promise 的回調、node 中的 process.nextTick 、對 Dom 變化監聽的 MutationObserver。
宏任務包括了 script 腳本的執行、setTimeout ,setInterval ,setImmediate 一類的定時事件,還有如 I/O 操作、UI 渲
染等。
~~~
詳細資料可以參考:[《瀏覽器事件循環機制(event loop)》](https://juejin.im/post/5afbc62151882542af04112d)[《詳解 JavaScript 中的 Event Loop(事件循環)機制》](https://zhuanlan.zhihu.com/p/33058983)[《什么是 Event Loop?》](http://www.ruanyifeng.com/blog/2013/10/event_loop.html)[《這一次,徹底弄懂 JavaScript 執行機制》](https://juejin.im/post/59e85eebf265da430d571f89)