1.js數據類型
6種原始類型:string,number,boolean,null,undefined,symbol(es6新增);
8種常見引用類型:Object,String,Number,Boolean,Function,Array,Date,RegExp;
判斷一個變量的類型使用typeof,typeof的返回值有string,number,boolean,undefined,symbol,object,function;
判斷一個對象的原型鏈上是否包含指定的構造函數;
注意:
typeof null === 'object'; //js歷史遺留BUG;
typeof Object=== 'function';typeof String=== 'function';typeof Array=== 'function' //因爲Object,String,Number,Boolean,Function,Array,Date,RegExp本身是一個構造函數;
typeof new Object()=== 'object';typeof new String()=== 'object';typeof new Array()=== 'object' //凡是new加構造函數生成的變量都是引用類型,使用typeof測試爲object,但是typeof new Function() === 'function';
typeof String()=== 'string';typeof Date()=== 'string';typeof Number()=== 'number';typeof Boolean()=== 'boolean';typeof Object()=== 'object';typeof Array()=== 'object';//不使用new直接使用構造函數生成的變量除了String,Number,Boolean,Date是原始類型外(使用instanceof時,String() instanceof String 爲false),其他的都是引用類型(使用instanceof時,Array() instanceof Array 爲true)。
2.new的作用
如下代碼爲例:
function Person(age){
this.age = age;
console.log(this);
return age;//如果此處返回的是基本數據類型,則不使用new時,直接返回name數據,使用時返回帶有name屬性的Person對象
}
Person.prototype.name = "20";
var person1 = new Person("16");
var person2 = Person("18");
console.log(person1);
console.log(person2);
運行結果如下:
結論:
不使用new時:函數的this指向當前運行上下文,執行完對this的添加屬性和方法後,返回函數的運行結果;
使用new時:函數的this指向一個空對象,執行完對this的添加屬性和方法後,返回this,忽略函數本身的return返回值;
new的作用:
1.創建一個空對象,並將this指向該對象
2.將this.__proto__指向該構造函數的prototype
3.調用構造函數爲該對象添加屬性和方法
4.如果構造函數中沒有return,返回this這個對象。如果return後面跟原始類型或this,返回this這個對象。如果return後面跟一個引用類型,則忽略掉this對象,返回這個引用類型。
3.new的簡單實現
代碼如下:
function _new(fn,...arg){
let obj = {};
obj.__proto__ = fn.prototype;
fn.apply(obj,arg);
return obj;
}
使用new生成的Person對象 使用_new生成的Person對象
4.前端緩存
4.1DNS緩存
DNS查找:瀏覽器DNS緩存->系統DNS緩存->DNS服務器
4.2CDN緩存(內容分發網絡)
源服務器的內容分發至分佈在各地的代理服務器,客戶從最近的一個代理服務器下載資源。
4.3HTTP緩存
cache-control:可控制是否緩存,如何緩存;
Expires:緩存過期時間;
if-none-Match/ETag:請求資源標識;
if-Modified-Since/Last-Modified:資源修改時間(只能精確到秒);
參考鏈接:https://www.jianshu.com/p/4f07740d68e4
4.4Service Worker
Service是一個註冊在指定源和路徑下的事件驅動Worker,後臺獨立運行的一個線程;由於不同系統以及不同瀏覽器的限制,目前可使用Service Sorker攔截處理全棧請求,替代HTTP緩存;
4.5PageCache與Ajax緩存
pagecache表示頁面緩存,當使用頁面緩存是,判斷頁面需要更新的模塊,使用ajax下載模塊,然後更新到相應的區域。
4.6瀏覽器本地緩存
cookie:key/value 單個cookie保存的數據不能超過4kb,每次訪問都要傳送cookie給服務器,可以設置過期時間,和域名綁定
localstorage:key/value 一直存儲於本地硬盤(瀏覽器中可以刪除),一般數據最大5MB(各個瀏覽器不一樣),和域名綁定
sessionStorage:key/value 關閉頁面或瀏覽器後被清除,最大5MB,和域名綁定
indexDB:key/object,可以存儲對象,瀏覽器中的數據庫,異步,支持事務,和域名綁定,存儲空間大
5.Promise的簡單實現
function Promise(fn){
let resolveCallbacks = [];
let rejectCallbacks = [];
this.then = (resloveCallback,rejectCallback) => {
resolveCallbacks.push(resloveCallback);
rejectCallbacks.push(rejectCallback);
return new Promise();
}
function resolve(res){
resolveCallbacks.forEach(callback=>{
callback(res);
})
}
function reject(res){
rejectCallbacks.forEach(callback=>{
callback(res);
})
}
fn(resolve,reject);
}
6、JS各大數據類型及其屬性、方法、應用;
6個原始類型:string,number,boolean,null,undefined,symbol;
8個對象類型:Object,Array,Function,String,Boolean,Number,Date,RegExp;
6.1 String
String構造函數方法
通過一串 Unicode 創建字符串。
String實例方法
返回特定位置的字符。
返回表示給定索引的字符的Unicode的值。
String.prototype.codePointAt()
返回使用UTF-16編碼的給定位置的值的非負整數。
連接兩個字符串文本,並返回一個新的字符串。
判斷一個字符串裏是否包含其他字符串。
判斷一個字符串的是否以給定字符串結尾,結果返回布爾值。
從字符串對象中返回首個被發現的給定值的索引值,如果沒有找到則返回-1。
String.prototype.lastIndexOf()
從字符串對象中返回最後一個被發現的給定值的索引值,如果沒有找到則返回-1。
String.prototype.localeCompare()
返回一個數字表示是否引用字符串在排序中位於比較字符串的前面,後面,或者二者相同。
使用正則表達式與字符串相比較。
返回調用字符串值的Unicode標準化形式。
在當前字符串尾部填充指定的字符串, 直到達到指定的長度。 返回一個新的字符串。
在當前字符串頭部填充指定的字符串, 直到達到指定的長度。 返回一個新的字符串。
返回指定重複次數的由元素組成的字符串對象。
被用來在正則表達式和字符串直接比較,然後用新的子串來替換被匹配的子串。
對正則表達式和指定字符串進行匹配搜索,返回第一個出現的匹配項的下標。
摘取一個字符串區域,返回一個新的字符串。
通過分離字符串成字串,將字符串對象分割成字符串數組。
判斷字符串的起始位置是否匹配其他字符串中的字符。
通過指定字符數返回在指定位置開始的字符串中的字符。
返回在字符串中指定兩個下標之間的字符。
String.prototype.toLocaleLowerCase()
根據當前區域設置,將符串中的字符轉換成小寫。對於大多數語言來說,toLowerCase
的返回值是一致的。
String.prototype.toLocaleUpperCase()
根據當前區域設置,將字符串中的字符轉換成大寫,對於大多數語言來說,toUpperCase
的返回值是一致的。
String.prototype.toLowerCase()
將字符串轉換成小寫並返回。
返回用字符串表示的特定對象。重寫 Object.prototype.toString
方法。
String.prototype.toUpperCase()
將字符串轉換成大寫並返回。
從字符串的開始和結尾去除空格。參照部分 ECMAScript 5 標準。
從字符串的左側去除空格。
從字符串的右側去除空格。
返回特定對象的原始值。重寫 Object.prototype.valueOf
方法。
6.2 Array
Array構造函數方法
從類數組對象或者可迭代對象中創建一個新的數組實例。
用來判斷某個變量是否是一個數組對象。
根據一組參數來創建新的數組實例,支持任意的參數數量和類型。
Array實例方法
修改器方法
下面的這些方法會改變調用它們的對象自身的值:
在數組內部,將一段元素序列拷貝到另一段元素序列上,覆蓋原有的值。
將數組中指定區間的所有元素的值,都替換成某個固定的值。
刪除數組的最後一個元素,並返回這個元素。
在數組的末尾增加一個或多個元素,並返回數組的新長度。
顛倒數組中元素的排列順序,即原先的第一個變爲最後一個,原先的最後一個變爲第一個。
刪除數組的第一個元素,並返回這個元素。
對數組元素進行排序,並返回當前數組。
在任意的位置給數組添加或刪除任意個元素。
在數組的開頭增加一個或多個元素,並返回數組的新長度。
訪問方法
下面的這些方法絕對不會改變調用它們的對象的值,只會返回一個新的數組或者返回一個其它的期望值。
返回一個由當前數組和其它若干個數組或者若干個非數組值組合而成的新數組。
判斷當前數組是否包含某指定的值,如果是返回 true
,否則返回 false
。
連接所有數組元素組成一個字符串。
抽取當前數組中的一段元素組合成一個新數組。
返回一個表示當前數組字面量的字符串。遮蔽了原型鏈上的 Object.prototype.toSource()
方法。
返回一個由所有數組元素組合而成的字符串。遮蔽了原型鏈上的 Object.prototype.toString()
方法。
Array.prototype.toLocaleString()
返回一個由所有數組元素組合而成的本地化後的字符串。遮蔽了原型鏈上的 Object.prototype.toLocaleString()
方法。
返回數組中第一個與指定值相等的元素的索引,如果找不到這樣的元素,則返回 -1。
返回數組中最後一個(從右邊數第一個)與指定值相等的元素的索引,如果找不到這樣的元素,則返回 -1。
迭代方法
在下面的衆多遍歷方法中,有很多方法都需要指定一個回調函數作爲參數。在每一個數組元素都分別執行完回調函數之前,數組的length屬性會被緩存在某個地方,所以,如果你在回調函數中爲當前數組添加了新的元素,那麼那些新添加的元素是不會被遍歷到的。此外,如果在回調函數中對當前數組進行了其它修改,比如改變某個元素的值或者刪掉某個元素,那麼隨後的遍歷操作可能會受到未預期的影響。總之,不要嘗試在遍歷過程中對原數組進行任何修改,雖然規範對這樣的操作進行了詳細的定義,但爲了可讀性和可維護性,請不要這樣做。
爲數組中的每個元素執行一次回調函數。
返回一個數組迭代器對象,該迭代器會包含所有數組元素的鍵值對。
如果數組中的每個元素都滿足測試函數,則返回 true
,否則返回 false。
如果數組中至少有一個元素滿足測試函數,則返回 true,否則返回 false。
將所有在過濾函數中返回 true
的數組元素放進一個新數組中並返回。
找到第一個滿足測試函數的元素並返回那個元素的值,如果找不到,則返回 undefined
。
找到第一個滿足測試函數的元素並返回那個元素的索引,如果找不到,則返回 -1
。
返回一個數組迭代器對象,該迭代器會包含所有數組元素的鍵。
返回一個由回調函數的返回值組成的新數組。
從左到右爲每個數組元素執行一次回調函數,並把上次回調函數的返回值放在一個暫存器中傳給下次回調函數,並返回最後一次回調函數的返回值。
從右到左爲每個數組元素執行一次回調函數,並把上次回調函數的返回值放在一個暫存器中傳給下次回調函數,並返回最後一次回調函數的返回值。
返回一個數組迭代器對象,該迭代器會包含所有數組元素的值。
6.3 Object
Object
構造函數的方法
通過複製一個或多個對象來創建一個新的對象。
使用指定的原型對象和屬性創建一個新對象。
給對象添加一個屬性並指定該屬性的配置。
給對象添加多個屬性並分別指定它們的配置。
返回給定對象自身可枚舉屬性的 [key, value]
數組。
凍結對象:其他代碼不能刪除或更改任何屬性。
Object.getOwnPropertyDescriptor()
返回對象指定的屬性配置。
返回一個數組,它包含了指定對象所有的可枚舉或不可枚舉的屬性名。
Object.getOwnPropertySymbols()
返回一個數組,它包含了指定對象自身所有的符號屬性。
返回指定對象的原型對象。
比較兩個值是否相同。所有 NaN 值都相等(這與==和===不同)。
判斷對象是否可擴展。
判斷對象是否已經凍結。
判斷對象是否已經密封。
返回一個包含所有給定對象自身可枚舉屬性名稱的數組。
防止對象的任何擴展。
防止其他代碼刪除對象的屬性。
設置對象的原型(即內部 [[Prototype]]
屬性)。
返回給定對象自身可枚舉值的數組。
7、JS異步編程
7.1 Promise對象
解決回調地獄問題,將回調形式改寫成鏈式調用;
new Promise((resolved,rejected)=>{
let i = 0;
setTimeout(()=>{
resolved(++i);
},1000)
}).then(res=>{
return new Promise((resolved,rejected)=>{
setTimeout(()=>{
console.log(res)
resolved(++res);
},1000)
});}).then(res=>{
return new Promise((resolved,rejected)=>{
setTimeout(()=>{
console.log(res)
resolved(++res);
},1000)
});}).then(res=>{
return new Promise((resolved,rejected)=>{
setTimeout(()=>{
console.log(res)
resolved(++res);
},1000)
});}).then(res=>{
setTimeout(()=>{
console.log(res)
},1000)})
//輸出 1 2 3 4
7.2 generator函數
generator函數可以把函數的執行流暫時掛起,等待合適的時機手動調用next()方法繼續執行yield關鍵字後面的語句,可使用next(data)將data作爲本次yield語句的返回值;generator函數返回一個可迭代的對象;
function *generator() {
let i = 0;
i = yield new Promise((resolve)=>{
setTimeout(() => {
resolve(++i);
},1000);
});
console.log(i);
i = yield new Promise((resolve)=>{
setTimeout(() => {
resolve(++i);
},1000);
});
console.log(i)
i = yield new Promise((resolve)=>{
setTimeout(() => {
resolve(++i);
},1000);
});
console.log(i)
i = yield new Promise((resolve)=>{
setTimeout(() => {
resolve(++i);
},1000);
});
console.log(i)
}
let run = (generator,res)=>{
var result = generator.next(res);
if(result.done) return;
result.value.then((res)=>{
run(generator,res);
});
}
run(generator());
8.3 async函數
async函數是generator函數的語法糖,自帶並自動調用類似於run的函數,執行完整個generator函數;
async function async1(){
let i = 0;
i = await new Promise((resolved)=>{
setTimeout(()=>{
resolved(++i)
},1000)
});
console.log(i)
i = await new Promise((resolved)=>{
setTimeout(()=>{
resolved(++i)
},1000)
});
console.log(i)
i = await new Promise((resolved)=>{
setTimeout(()=>{
resolved(++i)
},1000)
});
console.log(i)
i = await new Promise((resolved)=>{
setTimeout(()=>{
resolved(++i)
},1000)
});
console.log(i)
}
async1()
9、原型和原型鏈
原型:每一個構造函數都有一個顯示原型對象prototype,prototype對象的所有屬性和方法都會被構造函數new出來的實例繼承;每一個對象都有一個隱式原型對象__proto__,__proto__指向new出這個對象的構造函數;
原型鏈:每一個對象都有一個隱式原型對象__proto__指向他的原型對象prototype(new出這個對象的構造函數的prototype),prototype是一個對象,他也有一個隱式原型對象__proto__指向他自己的原型對象prototype;這樣一層一層的指向形成的一條鏈式指向叫做原型鏈;
總結三點:原文鏈接https://blog.csdn.net/u012443286/article/details/78823955
1.每個構造函數都有一個原型對象
2.原型對象都包含一個指向構造函數的指針
3.實例都包含一個指向原型對象的內部指針
10、對象的封裝與繼承
參考原文1:https://segmentfault.com/a/1190000013795390
參考原文2:https://www.jianshu.com/p/2aaaacd8bad3
class類:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes
特性1:class中定義的方法中的this如果沒有指定this的值,this值將爲undefined。class方法中的this只能指向class內部,不能指向外部;
例:
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
特性2:class定義的類不會存在變量提升;
特性3:使用 extends
繼承父類;
特性4:如果構造函數constructor中沒有return,返回this這個對象。如果return後面跟原始類型或this,返回this這個對象。如果return後面跟一個引用類型,則忽略掉this對象,返回這個引用類型。
特性5:super關鍵字用於訪問和調用一個對象的父對象上的函數。//
11、對象的深拷貝
方案1:只能實現屬性中沒有undefined
、function
、symbol
的深拷貝
方法:使用JSON.stringify/JSON.parse
侷限性:undefined
、function
、symbol
會在轉換過程中會被忽略;
const originObj = {
name:'axuebin',
sex:undefined,
sayHello:function(){
console.log('Hello World');
}
}
console.log(originObj); // {name: "axuebin", sex: undefined, sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}
方法2:只能實現屬性中沒有對象或數組的深拷貝
數組的深拷貝:let _newArray = oldArray.slice(); let _newArray2 = old.concat();
對象的深拷貝:let _newArray = Object.ossign({},oldObject);
侷限性:假如源對象的屬性值是一個指向對象的引用,它也只拷貝那個引用值;
方法3:遞歸實現任何對象的深拷貝
function deepClone(obj){ //可深度複製所有原始類型、數組、Object對象、函數、Date、
if(obj instanceof Object){
let new_obj = null;
switch(obj.constructor){
case Array:
new_obj = [];
for (let key of Object.keys(obj)){
new_obj[key] = deepClone(obj[key]);
}
break;
case Object:
new_obj = {};
for (let key of Object.keys(obj)){
new_obj[key] = deepClone(obj[key]);
}
break;
case Function:
new_obj = eval('(' + obj.toString() + ')');
break;
case Date:
new_obj = new Date(obj.getTime());
break;
default :
new_obj = obj;
}
return new_obj;
}else {
return obj;
}
}
12、閉包
閉包是對一個作用域(通常是函數作用域)中的變量引用的集合(對象或者函數);
MDN閉包鏈接:MDN閉包鏈接
例:使用閉包實現模塊化管理變量;
var Counter = (function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
13、JS事件
13.1 事件模型
1 DOM0模型
使用onclick在元素上綁定事件,或者使用ducument.getElementById('#id').onclick = function(){}綁定事件;
2 DOM2模型(W3C制定的標準模型)
使用document.getElementById('#id').addEventList(type,listener,options)綁定事件;其中type表示事件類型,例:click;其中listener表示觸發的行爲,例:function(){};其中options表示觸發的階段,例:false(表示冒泡階段觸發);
3 IE事件模型
13.2 事件委託
將多個子節點的事件委託給祖先節點處理;
例:將多個button子節點的事件委託給父節點'box'來處理;
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('刪除');
break;
case 'move' :
alert('移動');
break;
case 'select' :
alert('選擇');
break;
}
}
}
}
13.3 事件阻止
1.event.stopPropagation()方法
這是阻止事件的冒泡方法,不讓事件向documen上層蔓延,但是默認事件任然會執行,當你調用這個方法的時候,如果點擊一個連接,這個連接仍然會被打開。
2.event.preventDefault()方法
這是阻止默認事件的方法,調用此方法是,連接不會被打開,但是會發生冒泡,冒泡會傳遞到上一層的父元素,此方法可以控制一些按鈕在點擊時只觸發時間而不會引起表單的提交。
3.return false;
這個方法比較暴力,他會同事阻止事件冒泡也會阻止默認事件;寫上此代碼,連接不會被打開,事件也不會傳遞到上一層的父元素;可以理解爲return false就等於同時調用了event.stopPropagation()和event.preventDefault()
14、AMD、CommonJS與ES6模塊
異同:
CommonJS:同步加載適用於服務器端、模塊的導入導出 exports<->require;
AMD:異步加載適用於客戶端、模塊的導入導出 define<->require、依賴前置;
ES6模塊:ES6只規定了模塊的語法,並沒有規定模塊的加載方式(可自定義加載方式),模塊的導入導出 export<->import;
ES6模塊特點:
1.不管是否在代碼頭部添加 use scrict ,都啓用嚴格模式;
2.模塊內的變量爲局部變量,不影響全局變量;
3.每個模塊只加載一次,之後的加載直接從內存中獲取,即單例模式加載模塊;
4.import命令會提升到模塊頂部,首先執行;
ES6模塊的基本用法:
1.導出的變量和函數必須要有名稱(export default 命令另外考慮);
2.as可模塊進行導出或導入時對模塊變量重命名;
例:
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName } //myName變量導出時以變量名exportName導出
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom
使用 as 重新定義導出的接口名稱,隱藏模塊內部的變量
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js"; //myName變量導入時以變量名name2導入
console.log(name1);// Tom
console.log(name2);// Jerry
3. import命令的特點
3.1只讀:import導入的變量不可進行改寫,但可對其屬性進行改寫;
3.2單例模式:import再次導入同一模塊時,直接從內存中導入;
4.export default 命令:只能有一個,在導入時可使用任何變量名接收,不需要使用 { }。
5.require與import的異同
require默認導出模塊內的全部變量,import * as mod from 'model.js'導出全部變量(包含默認變量),import {a, b} from 'model.js'導出a和b變量,import mod from 'model.js'導出默認變量,import mod, {a, b} from 'model.js'導出默認變量和a,b變量。
15、JS編譯原理
15.1 js的編譯過程
1.詞法分析:將字符流轉換成記號流
2.語法分析:將記號流抽象成AST語法樹
3.預編譯:通過編譯器對所有的變量和函數進行聲明生成作用域;
4.解釋執行:在作用域及作用域鏈中查找所操作的變量及函數,然後進行操作;
15.2 JIT編譯優化
在js引擎執行代碼的時候增加一個監視器(也叫分析器),如多同一段代碼被運行了幾次就會被標記爲'warm',如果被運行了很多次就會被標記爲'hot';
基線編譯器:如果一段代碼被標記了'warm',JIT就把這段代碼送到基線編譯器去編譯,將編譯後的結果替換掉那段代碼;
優化編譯器:如果一段代碼被標記了'hot',JIT就把這段代碼送到優化編譯器去編譯,生成一個更快速和高效的代碼版替換掉那段代碼;
去優化:如果在執行的過程中,這段相同的代碼不再相同了,執行過程將會回到基線編譯器或解釋器,這一過程叫做去優化。
16、JS的AST抽象語法樹
JS的AST抽象語法樹是在對JS代碼進行此法分析後,進行語法分析生成的一個抽象語法樹。
npm上的recast工具可以將js代碼轉化爲AST語法樹,並可進行修改,再轉換成js代碼,也可以直接構建代碼。
17、async與defer的區別
先來試個一句話解釋仨,當瀏覽器碰到 script
腳本的時候:
-
<script src="script.js"></script>
沒有
defer
或async
,瀏覽器會立即加載並執行指定的腳本,“立即”指的是在渲染該script
標籤之下的文檔元素之前,也就是說不等待後續載入的文檔元素,讀到就加載並執行。 -
<script async src="script.js"></script>
有
async
,加載和渲染後續文檔元素的過程將和script.js
的加載與執行並行進行(異步)。異步加載,加載完成立即執行指定的腳本,不考慮HTML是否解析完成。 -
<script defer src="myscript.js"></script>
有
defer
,加載後續文檔元素的過程將和script.js
的加載並行進行(異步),但是script.js
的執行要在所有元素解析完成之後,DOMContentLoaded
事件觸發之前完成。異步加載,加載完成並在HTML解析後,按定義的順序執行指定的腳本。
然後從實用角度來說呢,首先把所有腳本都丟到 </body>
之前是最佳實踐,因爲對於舊瀏覽器來說這是唯一的優化選擇,此法可保證非腳本的其他一切元素能夠以最快的速度得到加載和解析。
接着,我們來看一張圖咯:
藍色線代表網絡讀取,紅色線代表執行時間,這倆都是針對腳本的;綠色線代表 HTML 解析。
此圖告訴我們以下幾個要點:
- defer 和 async 在網絡讀取(下載)這塊兒是一樣的,都是異步的(相較於 HTML 解析)
- 它倆的差別在於腳本下載完之後何時執行,顯然 defer 是最接近我們對於應用腳本加載和執行的要求的
- 關於 defer,此圖未盡之處在於它是按照加載順序執行腳本的,這一點要善加利用
- async 則是一個亂序執行的主,反正對它來說腳本的加載和執行是緊緊挨着的,所以不管你聲明的順序如何,只要它加載完了就會立刻執行
- 仔細想想,async 對於應用腳本的用處不大,因爲它完全不考慮依賴(哪怕是最低級的順序執行),不過它對於那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的,最典型的例子:Google Analytics
18、()小括號
var b = 10;
(function b() {
b = 20;
console.log(b)
})()
針對這題,在知乎上看到別人的回答說:
- 函數表達式與函數聲明不同,函數名只在該函數內部有效,並且此綁定是常量綁定。
- 對於一個常量進行賦值,在 strict 模式下會報錯,非 strict 模式下靜默失敗。
- IIFE中的函數是函數表達式,而不是函數聲明。