前端基本功-常見概念(一) 點這裏
前端基本功-常見概念(二) 點這裏
前端基本功-常見概念(三) 點這裏javascript
AMD:AMD規範採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。css
AMD是requirejs 在推廣過程當中對模塊定義的規範化產出,提早執行,推崇依賴前置。用define()定義模塊,用require()加載模塊,require.config()指定引用路徑等html
首先咱們須要引入require.js文件和一個入口文件main.js。main.js中配置require.config()並規定項目中用到的基礎模塊。前端
/** 網頁中引入require.js及main.js **/ <script src="js/require.js" data-main="js/main"></script> /** main.js 入口文件/主模塊 **/ // 首先用config()指定各模塊路徑和引用名 require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //實際路徑爲js/lib/jquery.min.js "underscore": "underscore.min", } }); // 執行基本操做 require(["jquery","underscore"],function($,_){ // some code here });
引用模塊的時候,咱們將模塊名放在[]
中做爲reqiure()
的第一參數;若是咱們定義的模塊自己也依賴其餘模塊,那就須要將它們放在[]
中做爲define()
的第一參數。vue
// 定義math.js模塊 define(function () { var basicNum = 0; var add = function (x, y) { return x + y; }; return { add: add, basicNum :basicNum }; }); // 定義一個依賴underscore.js的模塊 define(['underscore'],function(_){ var classify = function(list){ _.countBy(list,function(num){ return num > 30 ? 'old' : 'young'; }) }; return { classify :classify }; }) // 引用模塊,將模塊放在[]內 require(['jquery', 'math'],function($, math){ var sum = math.add(10,20); $("#sum").html(sum); });
CMD:seajs 在推廣過程當中對模塊定義的規範化產出,延遲執行,推崇依賴就近java
require.js在申明依賴的模塊時會在第一之間加載並執行模塊內的代碼:node
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等於在最前面聲明並初始化了要用到的全部模塊 if (false) { // 即使沒用到某個模塊 b,但 b 仍是提早執行了 b.foo() } });
CMD是另外一種js模塊化方案,它與AMD很相似,不一樣點在於:AMD 推崇依賴前置、提早執行,CMD推崇依賴就近、延遲執行。此規範實際上是在sea.js推廣過程當中產生的。jquery
/** CMD寫法 **/ define(function(require, exports, module) { var a = require('./a'); //在須要時申明 a.doSomething(); if (false) { var b = require('./b'); b.doSomething(); } }); /** sea.js **/ // 定義模塊 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; }); // 加載模塊 seajs.use(['math.js'], function(math){ var sum = math.add(1+2); });
CommonJs:Node.js是commonJS規範的主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:module、exports、require、global。實際使用時,用module.exports定義當前模塊對外輸出的接口(不推薦直接用exports),用require加載模塊。git
// 定義模塊math.js var basicNum = 0; function add(a, b) { return a + b; } module.exports = { //在這裏寫上須要向外暴露的函數、變量 add: add, basicNum: basicNum } // 引用自定義的模塊時,參數包含路徑,可省略.js var math = require('./math'); math.add(2, 5); // 引用核心模塊時,不須要帶路徑 var http = require('http'); http.createService(...).listen(3000);
commonJS用同步的方式加載模塊。在服務端,模塊文件都存在本地磁盤,讀取很是快,因此這樣作不會有問題。可是在瀏覽器端,限於網絡緣由,更合理的方案是使用異步加載。github
ES6 Module:ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,旨在成爲瀏覽器和服務器通用的模塊解決方案。其模塊功能主要由兩個命令構成:export和import。export命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。
/** 定義模塊 math.js **/ var basicNum = 0; var add = function (a, b) { return a + b; }; export { basicNum, add }; /** 引用模塊 **/ import { basicNum, add } from './math'; function test(ele) { ele.textContent = add(99 + basicNum); }
如上例所示,使用import命令的時候,用戶須要知道所要加載的變量名或函數名。其實ES6還提供了export default命令,爲模塊指定默認輸出,對應的import語句不須要使用大括號。這也更趨近於ADM的引用寫法。
/** export default **/ //定義輸出 export default { basicNum, add }; //引入 import math from './math'; function test(ele) { ele.textContent = math.add(99 + math.basicNum); }
ES6的模塊不是對象,import命令會被 JavaScript 引擎靜態分析,在編譯時就引入模塊代碼,而不是在代碼運行時加載,因此沒法實現條件加載。也正由於這個,使得靜態分析成爲可能。
CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
- 編譯時加載: ES6 模塊不是對象,而是經過 export 命令顯式指定輸出的代碼,import時採用靜態命令的形式。即在import時能夠指定加載某個輸出值,而不是加載整個模塊,這種加載稱爲「編譯時加載」。 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
本節參考文章:前端模塊化:CommonJS,AMD,CMD,ES6
ES5的繼承時經過prototype或構造函數機制來實現。ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.apply(this))。
ES6的繼承機制徹底不一樣,實質上是先建立父類的實例對象this(因此必須先調用父類的super()方法),而後再用子類的構造函數修改this。
具體的:ES6經過class關鍵字定義類,裏面有構造方法,類之間經過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,不然新建實例報錯。由於子類沒有本身的this對象,而是繼承了父類的this對象,而後對其進行加工。若是不調用super方法,子類得不到this對象。
ps:super關鍵字指代父類的實例,即父類的this對象。在子類構造函數中,調用super後,纔可以使用this關鍵字,不然報錯。
區別:(以SubClass,SuperClass,instance爲例)
ES5中繼承的實質是:(那種經典寄生組合式繼承法)經過prototype或構造函數機制來實現,先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.apply(this))。
ES6中繼承的實質是:先建立父類的實例對象this(因此必須先調用父類的super()方法),而後再用子類的構造函數修改this
靜態方法繼承實質上只須要更改下SubClass.__proto__到SuperClass便可
本節參考文章:連接
請求報文 | 響應報文 |
---|---|
請求行 請求頭 空行 請求體 | 狀態行 響應頭 空行 響應體 |
HTTP request報文結構是怎樣的
首行是Request-Line包括:請求方法,請求URI,協議版本,CRLF
首行以後是若干行請求頭,包括general-header,request-header或者entity-header,每一個一行以CRLF結束
請求頭和消息實體之間有一個CRLF分隔
根據實際請求須要可能包含一個消息實體 一個請求報文例子以下:
GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1 Host: www.w3.org Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 Referer: https://www.google.com.hk/ Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: authorstyle=yes If-None-Match: "2cc8-3e3073913b100" If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT name=qiu&age=25
請求報文
HTTP response報文結構是怎樣的
首行是狀態行包括:HTTP版本,狀態碼,狀態描述,後面跟一個CRLF
首行以後是若干行響應頭,包括:通用頭部,響應頭部,實體頭部
響應頭部和響應實體之間用一個CRLF空行分隔
最後是一個可能的消息實體 響應報文例子以下:
HTTP/1.1 200 OK Date: Tue, 08 Jul 2014 05:28:43 GMT Server: Apache/2 Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT ETag: "40d7-3e3073913b100" Accept-Ranges: bytes Content-Length: 16599 Cache-Control: max-age=21600 Expires: Tue, 08 Jul 2014 11:28:43 GMT P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml" Content-Type: text/html; charset=iso-8859-1 {"name": "qiu", "age": 25}
響應報文
工廠模式集中實例化了對象,避免實例化對象大量重複問題
//工廠模式 function createObject(a,b){ var obj = new Object(); //集中實例化 obj.a = a; obj.b = b; obj.c = function () { return this.a + this.b; }; return obj; //返回實例化對象 } var box = createObject('abc',10); var box1 = createObject('abcdef',20); alert(box.c()); //返回abc10 alert(box1.c()); //返回abcdef20
//構造函數 function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } var box = new Create('abc',10); alert(box.run()); //返回abc10
構造函數相比工廠模式:
構造函數編寫規範:
構造函數和普通函數的區別:
查看歸屬問題,要建立兩個構造函數:
function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } function DeskTop(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } var box = new Create('abc',10); var box1 = new DeskTop('def',20); alert(box instanceof Object); //這裏要注意:全部的構造函數的對象都是Object. alert(box instanceof Create); //true alert(box1 instanceof Create); //false alert(box1 instanceof DeskTop); //true
Promise.resolve()
能夠生成一個成功的Promise
Promise.resolve()語法糖
例1:Promise.resolve('成功')
等同於new Promise(function(resolve){resolve('成功')})
例2:
var resolved = Promise.resolve('foo'); resolved.then((str) => console.log(str);//foo )
至關於
var resolved = new Promise((resolve, reject) => { resolve('foo') }); resolved.then((str) => console.log(str);//foo )
Promise.resolve方法有下面三種形式:
這三種形式都會產生一個新的Promise。其中:
實際上第二種形式能夠歸在第三種形式中。
本節參考文章:ES6中的Promise.resolve()
推薦閱讀:性感的Promise...
僞類 用於當
已有元素
處於的某個狀態時,爲其添加對應的樣式,這個狀態是根據用戶行爲而動態變化的。
當用戶懸停在指定的元素時,咱們能夠經過 :hover
來描述這個元素的狀態。雖然它和普通的 CSS 類類似,能夠爲已有的元素添加樣式,可是它只有處於 DOM 樹沒法描述的狀態下才能爲元素添加樣式,因此將其稱爲僞類。
僞元素 用於建立一些
不在文檔樹中
的元素,併爲其添加樣式。
咱們能夠經過 :before
來在一個元素前增長一些文本,併爲這些文本添加樣式。雖然用戶能夠看到這些文本,可是這些文本實際上不在文檔樹中。
本節參考文章:前端面試題-僞類和僞元素、總結僞類與僞元素
DOM文檔加載的步驟爲:
觸發的時機不同,先觸發DOMContentLoaded事件,後觸發load事件。
原生js
// 不兼容老的瀏覽器,兼容寫法見[jQuery中ready與load事件](http://www.imooc.com/code/3253),或用jQuery document.addEventListener("DOMContentLoaded", function() { // ...代碼... }, false); window.addEventListener("load", function() { // ...代碼... }, false);
jQuery
// DOMContentLoaded $(document).ready(function() { // ...代碼... }); //load $(document).load(function() { // ...代碼... });
head 中資源的加載
body 中資源的加載
注意:
頁面中引用的js 代碼若是有異步加載的 js、css、圖片,是會影響 load 事件觸發的。video、audio、flash 不會影響 load 事件觸發。
推薦閱讀:再談 load 與 DOMContentLoaded
本節參考文章:DOMContentLoaded與load的區別、事件DOMContentLoaded和load的區別
由於瀏覽器生成Dom樹的時候是一行一行讀HTML代碼的,script標籤放在最後面就不會影響前面的頁面的渲染。那麼問題來了,既然Dom樹徹底生成好後頁面才能渲染出來,瀏覽器又必須讀徹底部HTML才能生成完整的Dom樹,script標籤不放在body底部是否是也同樣,由於dom樹的生成須要整個文檔解析完畢。
咱們再來看一下chrome在頁面渲染過程當中的,綠色標誌線是First Paint的時間。納尼,爲何會出現firstpaint,頁面的paint不是在渲染樹生成以後嗎?其實現代瀏覽器爲了更好的用戶體驗,渲染引擎將嘗試儘快在屏幕上顯示的內容。它不會等到全部HTML解析以前開始構建和佈局渲染樹。部分的內容將被解析並顯示。也就是說瀏覽器可以渲染不完整的dom樹和cssom,儘快的減小白屏的時間。假如咱們將js放在header,js將阻塞解析dom,dom的內容會影響到First Paint,致使First Paint延後。因此說咱們會 將js放在後面,以減小First Paint的時間,可是不會減小DOMContentLoaded被觸發的時間。
本節參考文章:DOMContentLoaded與load的區別
clientheight:內容的可視區域,不包含border。clientheight=padding+height-橫向滾動軸高度。
這裏寫圖片描述
offsetheight,它包含padding、border、橫向滾動軸高度。
offsetheight=padding+height+border+橫向滾動軸高度
scrollheight,可滾動高度,就是將滾動框拉直,再也不滾動的高度,這個很好理解。 It includes the element’s padding, but not its border or margin.
本節參考文章:css clientheight、offsetheight、scrollheight詳解
不容許重複的屬性名稱或參數值。當檢測到對象中重複命名的屬性,例如:
var object = {foo: "bar", foo: "baz"};)
或檢測到函數中重複命名的參數時,例如:
function foo(val1, val2, val1){})
嚴格模式會拋出錯誤,所以捕捉幾乎能夠確定是代碼中的bug能夠避免浪費大量的跟蹤時間。
本節參考文章:經典面試題(4)
JavaScript 處理未定義變量的方式比較寬鬆:未定義的變量會在全局對象建立一個新變量。在瀏覽器中,全局對象是 window 。
function foo(arg) { bar = "this is a hidden global variable"; }
真相是: ``` function foo(arg) { window.bar = "this is an explicit global variable"; } ``` 函數 foo 內部忘記使用 var ,意外建立了一個全局變量。此例泄漏了一個簡單的字符串,無傷大雅,可是有更糟的狀況。 另外一種意外的全局變量可能由 this 建立: ``` function foo() { this.variable = "potential accidental global"; } // Foo 調用本身,this 指向了全局對象(window) // 而不是 undefined foo(); ``` 在 JavaScript 文件頭部加上 'use strict',能夠避免此類錯誤發生。啓用嚴格模式解析 JavaScript ,避免意外的全局變量。
被遺忘的計時器或回調函數
在 JavaScript 中使用 setInterval 很是日常。一段常見的代碼:
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 處理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
此例說明了什麼:與節點或數據關聯的計時器再也不須要,node 對象能夠刪除,整個回調函數也不須要了。但是,計時器回調函數仍然沒被回收(計時器中止纔會被回收)。同時,someResource 若是存儲了大量的數據,也是沒法被回收的。
對於觀察者的例子,一旦它們再也不須要(或者關聯的對象變成不可達),明確地移除它們很是重要。老的 IE 6 是沒法處理循環引用的。現在,即便沒有明確移除它們,一旦觀察者對象變成不可達,大部分瀏覽器是能夠回收觀察者處理函數的。
觀察者代碼示例:
var element = document.getElementById('button'); function onClick(event) { element.innerHTML = 'text'; } element.addEventListener('click', onClick);
對象觀察者和循環引用注意事項
老版本的 IE 是沒法檢測 DOM 節點與 JavaScript 代碼之間的循環引用,會致使內存泄漏。現在,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法,已經能夠正確檢測和處理循環引用了。換言之,回收節點內存時,沒必要非要調用 removeEventListener 了。
脫離 DOM 的引用
有時,保存 DOM 節點內部數據結構頗有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組頗有意義。此時,一樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另外一個在字典中。未來你決定刪除這些行時,須要把兩個引用都清除。
var elements = { button: document.getElementById('button'), image: document.getElementById('image'), text: document.getElementById('text') }; function doStuff() { image.src = 'http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多邏輯 } function removeButton() { // 按鈕是 body 的後代元素 document.body.removeChild(document.getElementById('button')); // 此時,仍舊存在一個全局的 #button 的引用 // elements 字典。button 元素仍舊在內存中,不能被 GC 回收。 }
此外還要考慮 DOM 樹內部或子節點的引用問題。假如你的 JavaScript 代碼中保存了表格某一個 <td> 的引用。未來決定刪除整個表格的時候,直覺認爲 GC 會回收除了已保存的 <td> 之外的其它節點。實際狀況並不是如此:此 <td> 是表格的子節點,子元素與父元素是引用關係。因爲代碼保留了 <td> 的引用,致使整個表格仍待在內存中。保存 DOM 元素引用的時候,要當心謹慎。
閉包是 JavaScript 開發的一個關鍵方面:匿名函數能夠訪問父級做用域的變量。
避免濫用
本節參考文章:4類 JavaScript 內存泄漏及如何避免
js垃圾回收有兩種常見的算法:引用計數和標記清除。
垃圾收集器在運行時會給儲存在內存中的全部變量加上標記,而後會去掉環境中的變量以及被環境中的變量引用的變量的標記,當執行完畢那些沒有存在引用 沒法訪問的變量就被加上標記,最後垃圾收集器完成清除工做,釋放掉那些打上標記的變量所佔的內存。
function problem() { var A = {}; var B = {}; A.a = B; B.a = A; }
引用計數存在一個弊端就是循環引用問題(上邊)
標記清除不存在循環引用的問題,是由於當函數執行完畢以後,對象A和B就已經離開了所在的做用域,此時兩個變量被標記爲「離開環境」,等待被垃圾收集器回收,最後釋放其內存。
分析如下代碼:
function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Junga"); globalPerson = null;//手動解除全局變量的引用
在這個🌰中,變量globalPerson取得了createPerson()函數的返回的值。在createPerson()的內部建立了一個局部變量localPerson並添加了一個name屬性。因爲localPerson在函數執行完畢以後就離開執行環境,所以會自動解除引用,而對於全局變量來講則須要咱們手動設置null,解除引用。
不過,解除一個值的引用並不意味着自動回收該值所佔用的內存,解除引用真正的做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其收回。
本節參考文章:JavaScript的內存問題
本節參考文章:2018前端面試總結...
一般 SPA 中前端路由有2種實現方式:
下面就來介紹下這兩種方式具體怎麼實現的
window.history 對象包含瀏覽器的歷史,window.history 對象在編寫時可不使用 window 這個前綴。history是實現SPA前端路由是一種主流方法,它有幾個原始方法:
- history.back() - 與在瀏覽器點擊後退按鈕相同
在HTML5,history對象提出了 pushState() 方法和 replaceState() 方法,這兩個方法能夠用來向歷史棧中添加數據,就好像 url 變化了同樣(過去只有 url 變化歷史棧纔會變化),這樣就能夠很好的模擬瀏覽歷史和前進後退了,如今的前端路由也是基於這個原理實現的。
pushState(stateObj, title, url) 方法向歷史棧中寫入數據,其第一個參數是要寫入的數據對象(不大於640kB),第二個參數是頁面的 title, 第三個參數是 url (相對路徑)。
- stateObj :一個與指定網址相關的狀態對象,popstate事件觸發時,該對象會傳入回調函數。若是不須要這個對象,此處能夠填null。
關於pushState,有幾個值得注意的地方:
- pushState方法不會觸發頁面刷新,只是致使history對象發生變化,地址欄會有反應,只有當觸發前進後退等事件(back()和forward()等)時瀏覽器纔會刷新
replaceState(stateObj, title, url) 和pushState的區別就在於它不是寫入而是替換修改瀏覽歷史中當前紀錄,其他和 pushState如出一轍
- 定義:每當同一個文檔的瀏覽歷史(即history對象)出現變化時,就會觸發popstate事件。
<a class="api a">a.html</a> <a class="api b">b.html</a>
// 註冊路由 document.querySelectorAll('.api').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; if (!!(window.history && history.pushState)) { // 支持History API window.history.pushState({name: 'api'}, link, link); } else { // 不支持,可以使用一些Polyfill庫來實現 } }, false) }); // 監聽路由 window.addEventListener('popstate', e => { console.log({ location: location.href, state: e.state }) }, false)
popstate監聽函數裏打印的e.state即是history.pushState()裏傳入的第一個參數,在這裏即爲{name: 'api'}
url 中能夠帶有一個 hash http://localhost:9000/#/a.html
window 對象中有一個事件是 onhashchange,如下幾種狀況都會觸發這個事件:
- 直接更改瀏覽器地址,在最後面增長或改變#hash;
// 註冊路由 document.querySelectorAll('.api').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; location.hash = link; }, false) }); // 監聽路由 window.addEventListener('hashchange', e => { console.log({ location: location.href, hash: location.hash }) }, false)
本節參考文章:vue 單頁應用(spa)前端路由實現原理