ECMAScript 中全部數值都是以 IEEE-754 64 位格式存儲,但位操做符並不直接操做 64 位的值。而是先將 64 位的值轉換成 32 位的整數,而後執行操做,最後再將結果轉換爲 64 位。(對於開發人員 64 位存儲格式是透明的,所以整個過程像是隻存在 32 位的整數同樣)javascript
function func(para1, para2){ console.log('修改前', arguments , para1, para2); arguments [0] = 'arguements_0'; arguments [1] = 'arguements_1'; console.log('修改後', arguments , para1, para2); } func('para1')
基本類型:php
基本類型的值沒法添加屬性(儘管這樣不會報錯),eg:css
var name = "zhang"; name.age = 17; console.log(name.age);// undefined
引用類型:html
ECMAScript 中全部函數的參數都是按值傳遞的。前端
eg:這裏的 obj 形參是建立了一個指針讓他與 person 指針指向同一個地址,obj 和 person 都指向同一個地址,但改變 obj 的指向 person 不改變html5
function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg"; } var person = new Object(); setName(person); alert(person.name); //"Nicholas"
使用 var 聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就是函數的局部環境;在 with 語句中,最接近的環境是函數環境。若是初始化變量時沒有使用 var 聲明,該變量會自動被添加到全局環境。java
// 1.if if (true) { var color = "blue"; } alert(color); //"blue" // 2.for for (var i=0; i < 10; i++){ } alert(i); //10 // 3.function function add(num1, num2) { var sum = num1 + num2; return sum; } alert(sum); //因爲 sum 不是有效的變量,所以會致使錯誤
如今使用 ES6 的let
會聲明塊級做用域變量。node
經常使用標記清除,不經常使用引用計數git
若是 slice() 方法的參數中有一個負數,則用數組長度加上該數來肯定相應的位置。例如,在一個包含 5 項的數組上調用 slice(-2,-1) 與調用 slice(3,4) 獲得的結果相同。若是結束位置小於起始位置,則返回空數組。程序員
ECMAScript 5 還新增了兩個歸併數組的方法: reduce() 和 reduceRight()。這兩個方法都會迭代數組的全部項,而後構建一個最終返回的值。其中, reduce() 方法從數組的第一項開始,逐個遍歷到最後。而 reduceRight() 則從數組的最後一項開始,向前遍歷到第一項。
工廠模式(使用函數建立對象,爲對象添加屬性和方法而後返回對象,被構造函數模式取代)
function createPersion(name, age) { return { name, age } } var p1 = createPersion("zhang", 17); var p2 = createPersion("foo", 18);
構造函數模式(每一個實例不共用屬性和方法,JS 中函數是對象)
function Persion(name, age) { this.name = name; this.age = age; this.print = function () { console.log(this.name); } } var p1 = new Persion("zhang", 17); var p2 = new Persion("foo", 18);
原型模式(每一個實例共用一套屬性和方法)
function Persion() {} Persion.prototype.name = "zhang"; Persion.prototype.age = 17; Persion.prototype.print = function () { console.log(this.name); } var p1 = new Persion(); var p2 = new Persion();
組合使用構造函數模式和原型模式(既有共用又有不共用的屬性和方法)
這是目前使用最普遍的建立自定義類型的方法。
使用對象覆蓋原型(字面量方式修改原型)要注意已經建立的實例的constructor
指針不會自動變(仍是覆蓋前的原型)。
function Persion(name, age) { this.name = name; this.age = age; } Persion.prototype = { constructor: Persion, print: function () { console.log(this.name); } } var p1 = new Persion("zhang", 17); var p2 = new Persion("foo", 18);
動態原型模式(給原型添加屬性的操做放到構造函數內)
function Persion(name, age) { this.name = name; this.age = age; // 第一次執行時初始化 if (typeof this.print !== "function") { Persion.prototype.print = function () { console.log(this.name); } } } var p1 = new Persion("zhang", 17); var p2 = new Persion("foo", 18);
寄生構造函數模式(和工廠模式幾乎同樣,能夠爲對象建立構造函數)
function MyArray(){ let arr = new Array(); arr.push.apply(arr, arguments); arr.toPipeString = function(){ return this.join("|"); } return arr; } var marr = new MyArray(1,2,3); console.log(marr.toPipeString());
穩妥構造函數模式
this
對象。適合用在安全環境中,或防止數據被其餘程序改動。function Persion(name, age) { var o = {}; // 除了使用 print 沒有其餘方法能獲取到 name o.print = function () { console.log(name); } return o; } var p1 = new Persion("zhang", 17); var p2 = new Persion("foo", 18);
OO 語言支持兩種繼承:接口繼承和實現繼承。因爲函數沒有簽名,ES 沒法實現接口繼承。
原型鏈
有兩個問題:1 屬性共享, 2 沒法參數傳遞
function SuperType(para) { this.property = true; this.arr = [1, 2, 3]; this.para = para; } SuperType.prototype.getSuperProp = function () { return this.property; } function SubType() { this.subproperty = false; } // 繼承(這時 SubType.prototype.constructor 屬性沒有了,SubType.[[prototype]].constructor 爲 SuperType) SubType.prototype = new SuperType(); SubType.prototype.getSubProp = function () { return this.subproperty; } // 問題1:沒法向父類構造函數傳值 var instance1 = new SubType(); var instance2 = new SubType(); // 問題2:instance2.arr 也改變了 instance1.arr.push(4);
借用構造函數
和建立對象的構造函數模式的問題同樣,每一個實例不共用屬性和方法。
function SuperType(para) { this.para = para; this.getPara = function(){ return this.para; } } function SubType() { SuperType.apply(this, arguments); } // 能夠向父類構造函數傳值 var instance1 = new SubType("instance1"); var instance2 = new SubType("instance2");
組合繼承
原型鏈 + 借用構造函數
JS 中最經常使用的繼承模式。
function SuperType(para) { this.property = true; this.arr = [1, 2, 3]; this.para = para; } SuperType.prototype.getSuperProp = function () { return this.property; } function SubType() { SuperType.apply(this, arguments); this.subproperty = false; } // 繼承 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.getSubProp = function () { return this.subproperty; } // 能夠向父類構造函數傳值 var instance1 = new SubType("instance1"); var instance2 = new SubType("instance2"); // instance2.arr 不變 instance1.arr.push(4);
原型式繼承
有兩個問題:1 屬性會共享,2 每一個實例沒法共用方法
function createObj(obj) { function f() {}; f.prototype = obj; return new f(); } var person = { name: "zhang", age: [17] } // person、 zi 和 ma 共用引用屬性 age var zi = createObj(person); var ma = createObj(person); zi.age.push(18); ma.age.push(17); console.log(person.age);
寄生式繼承
和建立對象的寄生構造函數模式相似,也有屬性會共享的問題
function createObjParasitic(obj) { let o = Object.create(obj); // o 目前是以下對象: // { // [[prototype]] = obj // } o.sayHi = function () { console.log("hi") } return o; } var person = { name: "zhang", age: [17] } var zi = createObjParasitic(person); zi.sayHi();
寄生組合式繼承
寄生式繼承 + 組合式繼承
將new SuperType()
改成Object.create(SuperType.prototype)
減小一次父類的調用,還避免了在SubType.prototype
上添加沒必要要的屬性(如:下面的property
和arr
屬性)。
function inheritPrototype(sub, sup) { // 使 sub.prototype 的 [[prototype]] 「指針」指向 sup.prototype sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function SuperType() { this.property = true; this.arr = [1, 2, 3]; } SuperType.prototype.getSuperProp = function () { return this.property; } function SubType() { SuperType.apply(this, arguments); this.subproperty = false; } inheritPrototype(SubType, SuperType); console.log((new SubType()).getSuperProp());
定義函數有兩種形式:函數聲明(會函數聲明提高)和函數表達式。
閉包指有權訪問另外一個函數做用域中的變量的函數。
每一個函數在被調用時都會自動取得兩個特殊變量: this 和 arguments。內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能直接訪問外部函數中的這兩個變量。不過,把外部做用域中的 this 對象保存在一個閉包可以訪問到的變量裏,就可讓閉包訪問該對象了。
若是想訪問做用域中的 this 和 arguments 對象必須把她們保存在閉包能夠訪問的變量中。
var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { return function () { // this指向window return this.name; }; } }; console.log(object.getNameFunc()()); //"The Window"(在非嚴格模式下) var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { var that = this; return function () { // 閉包能夠訪問that // this指向window,that指向object return that.name; }; } }; console.log(object.getNameFunc()()); //"My Object"
JS 中沒有使用成員的概念,全部對象屬性都是共有的。但在函數中定義的變量均可以認爲是私有比阿尼浪,由於不能在函數外部訪問這些變量(包括:參數、局部變量和函數內部定義的其餘函數)。
JS 以字面量的方式建立單例對象。
var singleton = { name: "xxx", method: function () { // ... } }
// 這時 function(){...}() 是當作語句解析的 var application = function(){ //私有變量和函數 var components = new Array(); //初始化 components.push(new BaseComponent()); //建立 application 的一個局部副本 var app = new BaseComponent(); //公共接口 app.getComponentCount = function(){ return components.length; }; app.registerComponent = function(component){ if (typeof component == "object"){ components.push(component); } }; //返回這個副本 return app; }();
BOM 的核心是 window 對象,它表示瀏覽器的一個實例,同時也是 ES 中規定的 Global 對象。
若是頁面中包含框架,則每一個框架都擁有本身的 window 對象,而且保存在 frames 集合中。在 frames 集合中,能夠經過數值索引(從 0 開始,從左至右,從上到下)或者框架名稱來訪問相應的 window 對象。每一個 window 對象都有一個 name 屬性,其中包含框架的名稱。
在使用框架的狀況下,瀏覽器中會存在多個 Global 對象。在每一個框架中定義的全局變量會自動成爲框架中 window 對象的屬性。因爲每一個 window 對象都包含原生類型的構造函數,所以每一個框架都有一套本身的構造函數,這些構造函數一一對應,但並不相等。例如,
top.Object
並不等於top.frames[0].Object
。這個問題會影響到對跨框架傳遞的對象使用instanceof
操做符。
window.top
對象始終指向最外層框架,也就是瀏覽器窗口window.parent
指向父窗口window.self
指向 window窗口相對屏幕的位置:window.screenLeft
和window.screenTop
窗口位置移動(默認是禁用的):window.moveTo()
和window.moveBy()
瀏覽器窗口大小(不一樣瀏覽器對這些屬性的解釋不一樣):window.innerHeight
、window.innerWidth
、window.outerWidth
和window.outerWidth
頁面視口信息:window.document.documentElement.clientWidth
和window.document.documentElement.clientHeight
瀏覽器窗口大小改變(默認是禁用的):window.resizeTo()
和window.resizeBy()
通常認爲,使用超時調用來模擬間歇調用是一種最佳模式。在開發環境下,不多使用真正的間歇調用,緣由是後一個間歇調用可能會在前一個間歇調用結束以前啓動。而像下面示例中那樣使用超時調用,則徹底能夠避免這一點。因此,最好不要使用間歇調用。
// 超時調用模擬間歇調用 var num = 0; var max = 10; function incrementNumber() { num++; //若是執行次數未達到 max 設定的值,則設置另外一次超時調用 if (num < max) { setTimeout(incrementNumber, 500); } else { alert("Done"); } } setTimeout(incrementNumber, 500);
// 基本 location.assign("https://developer.mozilla.org") // 下面兩種和和顯示調用 assign 方法同樣 window.location = "https://developer.mozilla.org" location.href = "https://developer.mozilla.org" // 其餘 location.hash = "#123" location.pathname = "zh-CN" // replace 的 location 不記錄在歷史記錄中 location.replace("https://developer.mozilla.org")
獲取瀏覽器信息、主語言、插件、設置等信息。
用來獲取瀏覽器窗口外部的顯示器信息,如像素寬度、高度等。
// 跳轉到最近的 mdn 界面 history.go("developer.mozilla.org");
function isHostMethod(object, property) { var t = typeof object[property]; return t === 'function' || (!!(t === 'object' && object[property])) || t === 'unknown'; } console.log(isHostMethod([], 'find'));
// 是否有將屬性不列舉的bug var hasDontEnumQuirk = function() { var o = { // 這裏新的 toString 應該要列舉出來的,由於新的 toString 已經覆蓋了舊的 [[Enumerable]] false 的 toString // 但 IE8 及更低版本的瀏覽器會不將 toString 列舉。 toString: function () {} }; for (var prop in o) { if (prop === 'toString') { return false; } } return true; }(); console.log(hasDontEnumQuirk());
用戶代理檢測:檢測呈現引擎、瀏覽器、平臺、設備和操做系統
由於用戶代理字符串有很長的發展歷史,在此期間瀏覽器供應商試圖在用戶代理字符串中添加一些欺騙信息,讓網站相信本身是另外一種瀏覽器,使得用戶代理檢測比較複雜。但經過navigator.userAgent
(大部分信息在這個字符串中),navigator.platform
和window
的屬性仍是能夠檢測出來呈現引擎、瀏覽器、平臺、設備和操做系統這些信息的。
HTML 頁面中,文檔元素始終是<html>
元素,XML 中沒有預約義的元素,任何元素均可能成爲文檔元素。
nodeType
屬性每一個節點都有nodeType
屬性,用於表示節點類型。節點類型在 Node 類型中定義的下列 12 個數值常量表示,任何節點的類型必居其一。
參見:Node.nodeType - Web APIs | MDN
childNodes
屬性childNodes
屬性中保存着一個NodeList
對象。
NodeList
對象是一種類數組對象,但不是 Array 實例。
DOM 結構變化會自動反映在NodeList
對象中,也就是說NodeList
對象是動態變化的,而不是一張快照。
將NodeList
對象轉爲數組:
function convertToArray(nodes){ return Array.prototype.slice.call(nodes, 0); }
clonNode()
方法參數爲 true 或 false,true 執行深複製(克隆子節點),false 執行淺複製(不克隆子節點)。
clonNode()
方法不會複製 JS 的屬性(IE 存在 bug,會複製時間處理程序)。
normalize()
方法
處理文檔樹中的文本節點。
Document 接口繼承自 Node 接口,Document 接口描述了任何類型文檔的公共屬性和方法。
document 對象是 HTMLDocument 的一個實例。
dcoumentElement
屬性取得<html>
的引用
doctype
屬性取得<!DOCTYPE>
的引用
titel
屬性修改titel
屬性不會改變<title>
元素
domain
屬性因爲跨域安全限制,來自不一樣子域的頁面沒法經過 JS 通訊。而將每一個document.domain
設置爲相同的值,這些頁面就能夠互相訪問對方包含的 JS 對象了。
domain
屬性修改有兩個限制:1. 同源,2. 低級(eg:三級子域名p2p.wrox.com
)向高級(eg:二級子域名wrox.com
)。
write
方法頁面加載過程調用會動態加入內容,頁面加載結束後調用會重寫整個頁面內容。
全部 HTML 元素都是由 HTMLElement 類型或她的子類型表示。
getAttribute()
方法getAttribute()
方法返回屬性值,例如:使用屬性訪問 style 和 onclick 時分別返回對象和方法,使用getAttribute()
方法返回字符串。
attributes
屬性attributes
屬性包含一個 NamedNodeMap 對象,與 NodeList 相似是一個動態集合。
修改文本節點時,字符串會根據文檔類型進行編碼(將預留字符轉義)。
splitText()
方法調用這個方法原來的文本節點變爲從開始到指定位置以前的內容,返回剩下的內容。
在全部節點類型中,只有 DocumentFragment 在文檔中沒有對應的標記。 DOM 規定文檔片斷(document fragment)是一種 「輕量級」 的文檔,能夠包含和控制節點,但不會像完整的文檔那樣佔用額外的資源。
經過apppendChild()
或insertBefore()
將文檔片斷添加到文檔中時,只會將文檔片斷的子節點添加到響應位置上,文檔片斷自己不會成爲文檔的一部分。例如:
let fragment = document.createDocumentFragment(); let ul = document.createElement("ul"); let li = null; for (let i = 0; i < 3; i++) { li = document.createElement("l1"); li.appendChild(document.createTextNode("Item " + (i + 1))); fragment.appendChild(li); } document.body.appendChild(ul); ul.appendChild(fragment);
通常來講,儘可能減小訪問 NodeList 的次數。由於每次訪問 NodeList,都會運行一次基於文檔的查詢。因此能夠考慮將從 NodeList 中取得的值緩存起來。
對於元素間的空格,瀏覽器會當作文本節點(除了 IE9 以前的瀏覽器)。Element Traversal Specification定義了一組新屬性能夠只訪問 Element 節點。
遍歷子元素:
// 新 var child = document.body.firstElementChild; while(child != document.body.lastElementChild){ console.log(child); child = child.nextElementSibling; } // 舊 var child = document.body.firstChild; while(child != document.body.lastChild){ if(child.nodeType == 1){ console.log(child); } child = child.nextSibling; }
HTML5 也添加了輔助管理 DOM 焦點的功能。
首先就是
document.activeElement
屬性,這個屬性始終會引用 DOM 中當前得到了焦點的元素。元素得到焦點的方式有頁面加載、用戶輸入(一般是經過按 Tab 鍵)和在代碼中調用focus()
方法。另外是新增了
document.hasFocus()
方法,用於判斷文檔是否獲取焦點。
Document.characterSet
)scrollIntoView()
方法經過滾動瀏覽器窗口或某個容器元素,調用元素就能夠出如今視口中。
多數狀況下,均可以經過簡單地轉換屬性名的格式來實現轉換。其中一個不能直接轉換的 CSS 屬性就是 float。因爲 float 是 JavaScript 中的保留字,所以不能用做屬性名。「DOM2 級樣式」 規範規定樣式對象上相應的屬性名應該是 cssFloat; Firefox、 Safari、 Opera 和 Chrome 都支持這個屬性,而 IE 支持的則是 styleFloat。
實踐中,最好始終指定度量單位(標準模式下全部的度量值必須指定單位,混雜模式下能夠不指定單位)。
<div id="myDiv">myDiv</div> <script> var myDiv = document.getElementById("myDiv"); //浮動 myDiv.style.cssFloat = "left"; //背景顏色 myDiv.style.backgroundColor = "red"; //改變大小 myDiv.style.width = "100px"; myDiv.style.height = "200px"; //指定邊框 myDiv.style.border = "1px solid black"; </script>
Element.getBoundClientRect()
。NodeIterator 和 TreeWalker。IE 不支持!
爲了讓開發人員更方便地控制頁面,「DOM2 級遍歷和範圍」 模塊定義了 「範圍」(range)接口。經過範圍能夠選擇文檔中的一個區域,而沒必要考慮節點的界限(選擇在後臺完成,對用戶是不可見的)。在常規的 DOM 操做不能更有效地修改文檔時,使用範圍每每能夠達到目的。
因爲老版本瀏覽器不支持,所以不多有人使用事件捕獲。建議放心使用事件冒泡,特殊須要時使用事件捕獲。
IE 提出的事件流叫事件冒泡(event bubling),事件由最具體(深)的節點向不具體(文檔)節點傳播。
Netscape 提出的事件流叫事件捕獲(event capturing),思想是不太具體的節點應先接收到事件,具體的節點後接收到事件。
「DOM2 級事件」 規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。
PS:多數瀏覽器都實現了在捕獲階段觸發事件對象上的事件這種特定行爲。即便 「DOM2 級事件」 規範明確要求捕獲階段不會涉及事件目標。
諸如 click、load 和 mouseover 叫事件的名字。響應某個事件的函數叫作事件處理程序,事件處理程序的名字以‘’on「開頭。
某個元素支持的每種事件,均可以用一個與之相應事件處理程序同名的 HTML 特性來指定。
這樣指定事件處理程序:首先會建立一個封裝着元素屬性值的函數,其次這種方式動態建立的函數會拓展做用域,例如:
<!-- 1. 查看屬相和參數 --> <input type="button" value="click" onclick="console.log(arguments, event, this, value)"> <!-- 2. 拓展做用域 --> <form> <input type="text" name="uname" value="zzz"> <input type="button" value="click" onclick="console.log(uname.value)"> </form> <!-- 這個動態建立的函數像這樣拓展做用域: function(){ with(document){ with(this,form){ with(this){ console.log(uname.value) } } } } -->
使用 DOM0 級方法指定的事件處理程序被認爲是元素的方法。所以,這時候的事件處理程序是在
元素的做用域中運行;換句話說,程序中的 this 引用當前元素。來看一個例子。
<button id="myBtn">myBtn</button> <script> var btn = document.getElementById("myBtn"); btn.onclick = function(){ alert(this.id); //"myBtn" }; </script>
將事件處理程序屬性的值設置爲 null 能夠刪除經過 DOM0 級方法指定的事件處理程序:
btn.onclick = null;
「DOM2 級事件」 定義了兩個方法,用於處理指定和刪除事件處理程序的操做:
addEventListener()
和removeEventListener()
。全部 DOM 節點中都包含這兩個方法,而且它們都接受 3 個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是 true,表示在捕獲階段調用事件處理程序;若是是 false,表示在冒泡階段調用事件處理程序。
<button id="myBtn">myBtn</button> <script> var btn = document.getElementById("myBtn"); btn.addEventListener("click", function(){ alert(this.id); }, false); btn.addEventListener("click", function(){ alert("Hello world!"); }, false); </script>
刪除事件時必須傳入綁定的事件的 「指針」。
IE 實現了與 DOM 中相似的兩個方法:
attachEvent()
和detachEvent()
。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。因爲 IE8 及更早版本只支持事件冒泡,因此經過attachEvent()
添加的事件處理程序都會被添加到冒泡階段。
兼容 DOM 的瀏覽器會將一個 event 對象傳入到事件處理程序中。不管指定事件處理程序時使用什麼方法(DOM0 級或 DOM2 級),都會傳入 event 對象。
event 對象的屬性參見:Event - Web APIs | MDN
<button id="myBtn">myBtn</button> <script> var btn = document.getElementById("myBtn"); btn.onclick = function(event){ alert(event.type); //"click" }; btn.addEventListener("click", function(event){ alert(event.type); //"click" }, false); </script>
DOM0 事件處理程序:event 對象做爲 window 對象的一個屬性存在。
attachEvent()
添加事件處理程序:event 對象做爲參數傳入事件處理函數中,也能夠經過 window 對象的 event 屬相獲得。
DOM3 級事件類型:DOM-Level-3-Events
HTML 中沒法訪問 widow 元素,因此通常在 window 上面發生的任何事件均可以在<body>
元素中經過相應特性指定。
mousedown、mouseup、click 和 dbclick 順序:
若是 mousedown 或 mouseup 中的一個被取消,click 事件就不會觸發。
鍵盤事件:keydown、keypress、keyup
按下字符鍵時:會先觸發 keydown 而後觸發 keypress 最後觸發 keyup,按住不放時 keydown、keypress 會重複觸發。
按下非字符鍵時:會觸發 keydown 最後觸發 keyup,按住不放時 keydown 會重複觸發。
文本事件:textInput
contextmenu 事件:用戶打開上下文菜單時觸發(Windows 下是鼠標右鍵單擊,Mac 下時 Ctrl + 單擊),能夠阻止默認的上下文菜單。
beforeunload 事件:在頁面卸載前觸發,並彈出確認框讓用戶確認是否離開頁面。這個事件不能完全取消,由於那樣就至關於讓用戶就沒法離開當前頁面了。。
DOMContentLoaded 事件 :
window 的 load 事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會由於要加載的外部資源過多而頗費周折。而 DOMContentLoaded 事件則在造成完整的 DOM 樹以後就會觸發,不理會圖像、 JavaScript 文件、 CSS 文件或其餘資源是否已經下載完畢。與 load 事件不一樣,DOMContentLoaded 支持在頁面下載的早期添加事件處理程序,這也就意味着用戶可以儘早地與頁面進行交互。
readystatechange 事件:提供與文檔或元素加載狀態有關的信息。
pageshow 和 pagehide 事件:瀏覽器會緩存前進和後退的頁面(bfcache, before-forward chache),頁面位於 bfcache 中,load 事件不會觸發,pageshow 事件會觸發。
hashchange 事件:頁面 URL 中 "#" 後面的字符串發生變化時觸發。
智能手機和平板相關的事件,能夠監測橫豎屏切換、方向改變、移動。
觸摸事件
事件發生順序入下
手勢事件
對 「事件處理程序過多」 問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。例如, click 事件會一直冒泡到 document 層次。也就是說,咱們能夠爲整個頁面指定一個 onclick 事件處理程序,而沒必要給每一個可單擊的元素分別添加事件處理程序。
經過手動去除事件處理程序的引用防止內存泄露。
步驟(1,2 步的方法已通過時,推薦直接用 new 建立 event 對象):
createEvent()
方法建立 event 對象。initXXXEvent()
方法。dispatchEvent()
方法。例,模擬鼠標事件:
<!-- 新 --> <button id="btn" onclick="alert('clicked')">click me</button> <script> var btn = document.querySelector("#btn"); var event = new MouseEvent("click", {button: 0}); btn.dispatchEvent(event); </script> <!-- 過期 --> <button id="btn2" onclick="alert('clicked Deprecated')">click me</button> <script> var btn = document.querySelector("#btn2"); var event = document.createEvent("MouseEvents"); event.initMouseEvent("click", true, true, document.defaultView); btn.dispatchEvent(event); </script>
<form> input1: <input type="text" name="i1"> input2: <input type="text" name="i2" autofocus> <select name="s"> <option>zzz</option> <option>jjj</option> <option>fff</option> </select> </form> <script> // 經過document.forms能夠取得全部表單 var forms = document.forms; // form 的 submit() 方法*不會*觸發 submit 事件 forms[0].addEventListener("submit", () => alert('submit')); // 取消下面這個註釋會很是鬼畜 // forms[0].submit(); // form 的 reset() 方法*會*觸發 reset 事件 forms[0].addEventListener("reset", () => alert('reset')); forms[0].reset(); // form 的 elements 屬性是表單中元素的集合 var filed = forms[0].elements[1]; // 自動將焦點移動到輸入框 window.addEventListener("load", () => { // autofocus 屬性在支持她的瀏覽器中應該爲 true,不支持的爲空字符串 if(filed.autofocus !== true){ filed.focus(); } }); // change 事件對於輸入元素失去焦點(blur)觸發,對於選擇元素修改選項後觸發 forms[0].elements["i2"].addEventListener("change", () => { console.log("input change"); }); forms[0].elements["s"].addEventListener("change", () => { console.log("select change"); }); </script>
<form> <!-- size : 能夠顯示的字符數 --> <input type="text" name="i1" value="initial value" size="4"> <br> <!-- 第一個文本節點爲初始值 --> <textarea name="t">initial value</textarea> <br> <!-- 綁定限制輸入類型的事件 --> <input type="text" name="onlyNum"> <br> <!-- 正則驗證 --> <input type="text" pattern="\w+"> </form> <script> var forms = document.forms; var input = forms[0].elements[0]; var textarea = forms[0].elements[1]; var onlyNumInput = forms[0].elements[2]; // 不建議用 DOM 方法修改文本框的值(例如:修改 <textarea> 的第一個子節點,或使用 setAttribute() 來設置 value 屬性)。 // 由於這些修改不必定會反映在DOM中。 // 建議像下面這樣使用 value 屬性讀取和設置文本框的值。 textarea.value = "new initial value"; console.log(input.value); // 選擇文本 input.addEventListener("focus", (e) => { e.target.select(); }); // 取得選擇文本 console.log(input.value.substring(input.selectionStart, input.selectionEnd)); // 選擇部分文本 textarea.setSelectionRange(3, 9); textarea.focus(); // keydown 屏蔽字符(keypress 已經棄用了) onlyNumInput.addEventListener("keydown", (e) => { if(!/\d/.test(e.key) && !e.ctrlKey){ e.preventDefault(); } }); // 屏蔽粘貼的字符 onlyNumInput.addEventListener("paste", (e) => { if(!/\d/.test(e.clipboardData.getData("text"))){ e.preventDefault(); } }); </script>
appendChild()
、removeChild()
)或選擇框的方法(add()
、remove()
)appendChild()
將一個選擇框中的選項移動到另外一個選擇框insertBefore()
移動選項的位置目前尚未內置的表單序列化的方法,參見:HTMLFormElement - Web APIs | MDN。造序列化的輪子要注意制定表單字段的顯示隱藏、是否禁用、按鈕、單選多選等狀況的處理。
設置 iframe 的 designMode 屬性可使這個 iframe 可編輯。
經過設置元素的 contenteditable 屬性控制該元素是否可編輯。
使用document.execCommand()
執行預約義的命令。
使用 Selection 和 Range 的相關的屬性和方法,參見:Selection - Web APIs | MDN、Range - Web APIs | MDN。
設置選擇的文本背景爲黃色:
<div id="rechedit" contenteditable="true" style="background: #ddd;"> Hello world! </div> <button onclick="setbg()">setbg</button> <script> function setbg(){ var selection = document.getSelection(), range = selection.getRangeAt(0); span = document.createElement("span"); // 設置背景 span.style.backgroundColor = "yellow"; // 設置到選區 range.surroundContents(span); } </script>
因爲富文本編輯是使用 iframe 而非表單控件實現的,所以從技術上說,富文本編輯器並不屬於表單。換句話說,富文本編輯器中的 HTML 不會被自動提交給服務器,而須要咱們手工來提取並提交 HTML。爲此,一般能夠添加一個隱藏的表單字段,讓它的值等於從 iframe 中提取出的 HTML。具體來講,就是在提交表單以前,從 iframe 中提取出 HTML,並將其插入到隱藏的字段中。
在使用<canvas>
前首先要檢測getContext()
方法是否存在。
使用toDataURL()
方法能夠導出在<canvas>
上繪製的圖形。
若是你知道未來還要返回某組屬性與變換的組合,能夠調用 save() 方法。調用這個方法後,當時的全部設置都會進入一個棧結構,得以妥善保管。而後能夠對上下文進行其餘修改。等想要回到以前保存的設置時,能夠調用 restore() 方法,在保存設置的棧結構中向前返回一級,恢復以前的狀態。連續調用 save() 能夠把更多設置保存到棧結構中,以後再連續調用 restore() 則能夠一級一級返回。
<canvas id="drawing" width=" 200" height="200">A drawing of something.</canvas> <script> var drawing = document.getElementById("drawing"); //肯定瀏覽器支持<canvas>元素 if (drawing.getContext){ var context = drawing.getContext("2d"); context.fillStyle = "#ff0000"; context.save(); //save1 context.fillStyle = "#00ff00"; context.translate(100, 100); context.save(); //save2 context.fillStyle = "#0000ff"; context.fillRect(0, 0, 100, 200); //從點(100,100)開始繪製藍色矩形 context.restore(); //返回save2 context.fillRect(10, 10, 100, 200); //從點(110,110)開始繪製綠色矩形 context.restore(); //返回save2 context.fillRect(0, 0, 50, 50); //從點(0,0)開始繪製紅色矩形 } </script>
使用getImageData
能夠獲取<canvas>
的ImageData實例(包括圖像的寬高和每一個像素的信息)。
使用putImageData()
能夠將ImageData實例設置到<canvas>
。
WebGL 涉及的複雜計算須要提早知道數值的精度,而標準的 JS 數值沒法知足。爲此 WebGL 引入了類型化數組。
gl
的compileShader()
方法進行編譯。跨文檔消息傳送(cross-document messaging),有時候簡稱爲 XDM,指的是在來自不一樣域的頁面間傳遞消息。例如, www.wrox.com 域中的頁面與位於一個內嵌框架中的 p2p.wrox.com 域中的頁面通訊。在 XDM 機制出現以前,要穩妥地實現這種通訊須要花不少工夫。 XDM 把這種機制規範化,讓咱們能既穩妥又簡單地實現跨文檔通訊。
XDM 的核心是
postMessage()
方法。在 HTML5 規範中,除了 XDM 部分以外的其餘部分也會提到這個方法名,但都是爲了同一個目的:向另外一個地方傳遞數據。對於 XDM 而言, 「另外一個地方」 指的是包含在當前頁面中的<iframe>
元素,或者由當前頁面彈出的窗口。
發送消息:iframe 的 contentWindow 的postMessage()
方法
處理消息:window 的onmessage
事件
拖動某元素時,將依次觸發下列事件:
當某個元素被拖動到一個有效的放置目標上時,下列事件會依次發生:
使用DragEvent
的dataTransfer
屬性的setData()
和getData()
方法能夠實現拖放的數據交換。
默認狀況下圖片、連接文本能夠拖動,能夠經過設置draggable
屬性爲true
使其餘元素可拖動。
<div id="destination" style="width: 200px; height: 200px; background: green;" ondrop="alert(event.dataTransfer.getData('text'))" >destination</div> <div id="box" style="display: inline; background: lightyellow; cursor: all-scroll;" draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'test text')" >drag me to destination</div>
<video>
和<audio>
這兩個媒體元素都有一個canPlayType()
方法檢測瀏覽器是否支持某種格式和解碼器。
Audio 不用像 Image 那樣必須插入到文檔中,只要建立一個實例並傳入音頻便可使用。
history.pushState()
方法增長曆史狀態popState
事件注意:請確保每一個pushState()
創造的假的 URL,服務器上都有一個真的 URL 與之對應, 否側用戶點擊刷新會致使 404 錯誤。
ECMA-262 第 3 版引入了 try-catch 語句,做爲 JavaScript 中處理異常的一種標準方式。這與 Java 中的 try-catch 語句是徹底相同的。
try{ // 可能會致使錯誤的代碼 } catch(error){ // 在錯誤發生時怎麼處理 alert(error.message) // message 屬性是惟一一個可以保證全部瀏覽器都支持的屬性 }
只要代碼中包含 finally 子句,則不管 try 或 catch 語句塊中包含什麼代碼——甚至 return 語句,都不會阻止 finally 子句的執行。
<script> alert(testFinally());// 0 function testFinally(){ try { return 2;// 不執行 alert('try');// 不執行 } catch (error){ return 1; } finally { return 0; } } </script>
只要代碼中包含 finally 子句,不管 try 仍是 catch 語句塊中的 return 語句都會被忽視。
與 try-catch 語句相匹配的還有 throw 操做符,用於拋出自定義錯誤。
遇到 throw 操做符時代碼會當即中止,僅當有 try-catch 語句捕獲到拋出的值時,代碼纔會繼續執行。
利用原型鏈能夠經過繼承 Error 來建立自定義錯誤類型。
<!-- 圖像 --> <img src="xxx" onerror="console.log('img not loaded')"> <!-- window --> <script> window.onerror = function(message, source, lineno, colno, error) { console.log(message); } throw "test"; </script>
GlobalEventHandlers.onerror - Web APIs | MDN
encodeURIComponent()
處理非致命錯誤,能夠根據下列一或多個條件來肯定:
eg:非致命錯誤添加 try-catch 可使非致命錯誤發生後後續代碼繼續執行,後面的模塊繼續加載
致命錯誤,能夠經過如下一或多個條件來肯定:
推薦將 JS 錯誤寫回服務器,把先後端的錯誤集中起來可以極大地方便對數據的分析。
創建這樣一種 JavaScript 錯誤記錄系統,首先須要在服務器上建立一個頁面(或者一個服務器入口點),用於處理錯誤數據。這個頁面的做用無非就是從查詢字符串中取得數據,而後再將數據寫入錯誤日誌中。這個頁面可能會使用以下所示的函數:
function logError(sev, msg){ var img = new Image(); img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" + encodeURIComponent(msg); }
這個 logError() 函數接收兩個參數:表示嚴重程度的數值或字符串(視所用系統而異)及錯誤消息。其中,使用了 Image 對象來發送請求,這樣作很是靈活,主要表現以下幾方面。
E4X 已經廢棄了(E4X - Archive of obsolete content | MDN),大概看了一下感受 JSX 和 XML 字面量有點像啊(PS:確實只是有點像,JSX | XML-like syntax extension to ECMAScript)。
JSON 的語法能夠表示如下三種類型的值。
與 JavaScript 的對象字面量相比, JSON 對象有兩個地方不同。首先,沒有聲明變量(JSON 中沒有變量的概念)。其次,沒有末尾的分號(由於這不是 JavaScript 語句,因此不須要分號)。再說一遍,對象的屬性必須加雙引號,這在 JSON 中是必需的。屬性的值能夠是簡單值,也能夠是複雜類型值。
與 XML 數據結構解析成 DOM 文檔提取數據很麻煩,而 JSON 解析爲 JS 對象提取數據很是簡單。
早期 JSON 解析器基本上就是使用 JS 的eavl()
函數,因爲 JSON 是 JS 語法的子集,所以eavl()
函數能夠解析並返回數據。ES5 對即系解析 JSON 的行爲進行規範,定義了全局對象 JSON。
JSON 對象有兩個方法:stringify()
和parse()
。
JSON.stringify()
除了要序列化的 JavaScript 對象外,還能夠接收另外兩個參數,這兩個參數用於指定以不一樣的方式序列化 JavaScript 對象。第一個參數是個過濾器,能夠是一個數組,也能夠是一個函數;第二個參數是一個選項,表示是否在 JSON 字符串中保留縮進。
能夠爲對象添加toJSON()
方法,這個方法會返回自身的 JSON 數據格式。
例如:
var purple = { name: "zzz", age: 17, toJSON() { return { age: this.age, fans: [] } } }; JSON.stringify(purple, (k, v) => k === "age" ? 99999 : v, 4); // '{\n "age": 99999,\n "fans": []\n}'
JSON.parse()
方法也能夠接收另外一個參數,該參數是一個函數,將在每一個鍵值對上調用。
例如:
var purple = { name: "zzz", age: 17, birthday: new Date(0) }; var str = JSON.stringify(purple); var obj = JSON.parse(str, (k, v) => k === "birthday" ? new Date(v) : v); console.log(obj.birthday.getFullYear());// 1970
XMLHttpRequest - Web APIs | MDN
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("GET", "example.txt", true); xhr.send(null);
查詢字符串中每一個參數的名稱和值必須使用
encodeURIComponent()
進行編碼而後放到 URL 的末尾。
open()
方法時第一個參數爲 GET(書上是小寫,應該是打錯了)POST 請求應該把數據做爲請求主體提交。
POST 請求的主體能夠包含很是多的數據,並且格式不限。
open()
方法時第一個參數爲 POST(書上是小寫,應該是打錯了)send()
方法發送數據<form> <input type="text" name="inp"> <input type="radio" name="ra" value="male" checked> <input type="radio" name="ra" value="famale"> <select name="sel"> <option>t1</option> <option>t2</option> </select> </form> <script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("POST", "example.php"); xhr.send(new FormData(document.forms[0])); </script>
XMLHttpRequest: progress event - Web APIs | MDN
當請求接收到數據這個事件會按期觸發。
CORS(Cross-Origin Resource Sharing,跨源資源共享)是 W3C 的一個工做草案,定義了在必須訪問跨源資源時,瀏覽器與服務器應該如何溝通。 CORS 背後的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,仍是應該失敗。
好比一個簡單的使用 GET 或 POST 發送的請求,它沒有自定義的頭部,而主體內容是 text/plain。在發送該請求時,須要給它附加一個額外的 Origin 頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。下面是 Origin 頭部的一個示例:
Origin: http://www.nczonline.net
若是服務器認爲這個請求能夠接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源信息(若是是公共資源,能夠回發 "*")。例如:
Access-Control-Allow-Origin: http://www.nczonline.net
若是沒有這個頭部,或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求。正常狀況下,瀏覽器會處理請求。注意,請求和響應都不包含 cookie 信息。
瀏覽器對 CORS 的實現:
要請求位於另外一個域中的資源,使用標準的 XHR 對象並在
open()
方法中傳入絕對 URL 便可:
var xhr = new XMLHttpRequest(); xhr.onload = function(){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } }; xhr.open("GET", "http://www.somewhere-else.com"); xhr.send(null);
跨越 XHR 的限制:
setRequestHeader()
設置自定義請求頭getAllResponseHeaders()
總會返回空字符串Preflight request - MDN Web Docs Glossary: Definitions of Web-related terms | MDN
一個 CORS 預檢請求是用於檢查服務器是否支持 CORS 即跨域資源共享。
它通常是用瞭如下幾個 HTTP 請求首部的
OPTIONS
請求:Access-Control-Request-Method
和Access-Control-Request-Headers
,以及一個Origin
首部。當有必要的時候,瀏覽器會自動發出一個預檢請求;因此在正常狀況下,前端開發者不須要本身去發這樣的請求。
默認狀況下,跨域請求不提供憑據(cookie,HTTP 認證,及客戶端 SSL 證實等)。經過將 withCredentials 設置爲 true,能夠指定某個請求應該發送憑據。
IE10 及更早版本都不支持!
沒法處理相應的信息,只能使用 onload 和 onerror 肯定是否接收到相應,所以只能用於瀏覽器與服務器單向通訊。
var img = new Image(); img.onload = img.onerror = function(){ alert("Done!"); }; img.src = "http://www.example.com/test?name=Nicholas";
例:經過查詢地理定位服務來顯示你的 IP 地址和位置信息。(接口已經廢棄:apilayer/freegeoip: IP geolocation web server)
function handleResponse(response){ alert("You’ re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name); } var script = document.createElement("script"); script.src = "http://freegeoip.net/json/?callback=handleResponse"; document.body.insertBefore(script, document.body.firstChild);
問題:
Ajax 是一種從頁面向服務器請求數據的技術,而 Comet 則是一種服務器向頁面推送數據的技術。 Comet 可以讓信息近乎實時地被推送到頁面上,很是適合處理體育比賽的分數和股票報價。
實現方式:
面對某個具體的用例,在考慮是使用 SSE 仍是使用 Web Sockets 時,能夠考慮以下幾個因素。
首先,你是否有自由度創建和維護 Web Sockets 服務器?由於 Web Socket 協議不一樣於 HTTP,因此現有服務器不能用於 Web Socket 通訊。 SSE 卻是經過常規 HTTP 通訊,所以現有服務器就能夠知足需求。
第二個要考慮的問題是到底需不須要雙向通訊。若是用例只需讀取服務器數據(如比賽成績),那麼 SSE 比較容易實現。若是用例必須雙向通訊(如聊天室),那麼 Web Sockets 顯然更好。別忘了,在不能選擇 Web Sockets 的狀況下,組合 XHR 和 SSE 也是能實現雙向通訊的。
對於未被受權系統有權訪問某個資源的狀況,咱們稱之爲 CSRF(Cross-Site Request Forgery,跨站點請求僞造)。
爲了確保經過 XHR 訪問的 URL 安全,通行的作法就是驗證發送請求者是否有權訪問相應的資源。有如下方法可供選擇:
Object.prototype.toString.call(JSON); // '[object JSON]' JSON = function(){}; Object.prototype.toString.call(JSON); // '[object Function]'
瀏覽器控制檯運行下面的例子,你會神奇地發現頁面竟然跳轉了(好神奇 =_=!!!)
function Persion(loc){ this.location = loc; } var zi = Persion('zi');// 不當心忘記使用 new
這時您須要用做用域安全的構造函數,而後你會神奇地發現頁面又跳轉了(好神奇 =_=!!!)
function Persion(loc){ if(this instanceof Persion){ this.location = loc; } else { return new Persion(loc) } } var zi = Persion('zi');// 又不當心忘記使用 new
使用做用域安全的構造函數後您就必需要用原型鏈了。
function Persion(loc){ if(this instanceof Persion){ this.location = loc; } else { return new Persion(loc) } } function ZM(loc){ Persion.call(this, loc); this.age = 17; } ZM.prototype = new Persion(); // 不將原型鏈連上就沒法繼承 ZM.prototype.constructor = ZM; var zi = new ZM('zi');
惰性載入表示函數執行的分支僅會發生一次。有兩種實現惰性載入的方式
第一種就是在函數被調用時再處理函數。在第一次調用的過程當中,該函數會被覆蓋爲另一個按合適方式執行的函數,這樣任何對原函數的調用都不用再通過執行的分支了。
第二種實現惰性載入的方式是在聲明函數時就指定適當的函數。這樣,第一次調用函數時就不會損失性能了,而在代碼首次加載時會損失一點性能。
// 第一種方式 function flatArr(arr){ if(typeof Array.prototype.flat === "function"){ flatArr = arr => arr.flat(); }else{ flatArr = arr => { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat const stack = [...arr]; const res = []; while (stack.length) { // pop value from stack const next = stack.pop(); if (Array.isArray(next)) { // push back array items, won't modify the original input stack.push(...next); } else { res.push(next); } } //reverse to restore input order return res.reverse(); } } return flatArr(arr); } flatArr([1, 2, 3, [4, 5, 6]]);
// 第二種方式 var flatArr = (function(){ if(typeof Array.prototype.flat === "function"){ return arr => arr.flat(); }else{ return arr => { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat const stack = [...arr]; const res = []; while (stack.length) { // pop value from stack const next = stack.pop(); if (Array.isArray(next)) { // push back array items, won't modify the original input stack.push(...next); } else { res.push(next); } } //reverse to restore input order return res.reverse(); } } })(); flatArr([1, 2, 3, [4, 5, 6]]);
另外一個日益流行的高級技巧叫作函數綁定。函數綁定要建立一個函數,能夠在特定的 this 環境中以指定參數調用另外一個函數。該技巧經常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同時保留代碼執行環境。
使用函數的bind()
方法。被綁定函數與普通函數相比須要更多開銷,最好只在必要時使用。
var handler = { msg: "text", handleClick: function(){ alert(this.msg) } } // not bind document.addEventListener('click', handler.handleClick); // bind document.addEventListener('click', handler.handleClick.bind(handler));
函數柯里化(function currying),用於建立已經設置好一個或多個參數的函數。
function curry(fn, ...args){ return function(...innerArgs){ return fn(...args, ...innerArgs); } } function add(a, b){ return a + b; } var curriedAdd = curry(add, 3); console.log(curriedAdd(5)); // 8 // 精簡版 var add2 = a => b => a + b; console.log(add2(3)(5)); // 8
注意:一旦將對象定義爲防篡改就沒法撤銷了。
禁止添加屬性。
Object.preventExtensions() - JavaScript | MDN
禁止添加屬性,且已有成員的[[Configurable]]
特性設爲爲false
(不能刪除和修改訪問器屬性)。
Object.seal() - JavaScript | MDN
禁止添加屬性,且已有成員的[[Configurable]]
特性設爲爲false
(不能刪除和修改訪問器屬性),且已有成員的[[[Writable]]
特性設爲false
(若是定義[[Set]]
函數,訪問器依然是可寫的)。
Object.freeze() - JavaScript | MDN
JS 是單線程的,setTimeout()
和setInterval()
只是在它們開始執行後過了設置的時間,將回調函數添加到事件隊列。若是尚未到設置的事件就清除了,這個回調函數就不會添加到事件隊列了。
使用setTimeout()
代替setInterval()
能夠解決setInterval()
在任務隊列已有該回調函數時不會重複添加的問題。
運行在瀏覽器中的 JavaScript 都被分配了一個肯定數量的資源。不一樣於桌面應用每每可以隨意控制他們要的內存大小和處理器時間, JavaScript 被嚴格限制了,以防止惡意的 Web 程序員把用戶的計算機搞掛了。其中一個限制是長時間運行腳本的制約,若是代碼運行超過特定的時間或者特定語句數量就不讓它繼續執行。若是代碼達到了這個限制,會彈出一個瀏覽器錯誤的對話框,告訴用戶某個腳本會用過長的時間執行,詢問是容許其繼續執行仍是中止它。全部 JavaScript 開發人員的目標就是,確保用戶永遠不會在瀏覽器中看到這個使人費解的對話框。定時器是繞開此限制的方法之一。
不需同步和按順序完成的循環可使用定時器進行分割,這時一種叫數組分塊(array chunking)的技術。
function chunk(arr, process, context){ setTimeout(function(){ let item = arr.shift(); process.call(context, item); if(arr.length){ setTimeout(arguments.callee, 100) } }, 100) } chunk([1, 2, 3], d => console.log(d));
瀏覽器中某些計算和處理要比其餘的昂貴不少。例如, DOM 操做比起非 DOM 交互須要更多的內存和 CPU 時間。連續嘗試進行過多的 DOM 相關操做可能會致使瀏覽器掛起,有時候甚至會崩潰。尤爲在 IE 中使用 onresize 事件處理程序的時候容易發生,當調整瀏覽器大小的時候,該事件會連續觸發。在 onresize 事件處理程序內部若是嘗試進行 DOM 操做,其高頻率的更改可能會讓瀏覽器崩潰。爲了繞開這個問題,你可使用定時器對該函數進行節流。
函數節流背後的基本思想是指,某些代碼不能夠在沒有間斷的狀況連續重複執行。第一次調用函數,建立一個定時器,在指定的時間間隔以後運行代碼。當第二次調用該函數時,它會清除前一次的定時器並設置另外一個。若是前一個定時器已經執行過了,這個操做就沒有任何意義。然而,若是前一個定時器還沒有執行,其實就是將其替換爲一個新的定時器。目的是隻有在執行函數的請求中止了一段時間以後才執行。
PS:書中的函數節流應該叫防反彈比較合適。
// 書中將下面的函數叫作 throttle function debounce(callback){ let timeoutID; function wrapper () { clearTimeout(timeoutID); timeoutID = setTimeout(callback, 1000); } return wrapper; } function throttle(callback){ let lastExec = 0; function wrapper () { if(Date.now() - lastExec > 1000){ lastExec = Date.now(); setTimeout(callback, 1000); } } return wrapper; } var resizeThrottle = throttle(() => console.log('throttle')); var resizeDebounce = debounce(() => console.log('debounce')); window.onscroll = () => { resizeThrottle(); resizeDebounce(); }
事件是一種叫作觀察者的設計模式,這是一種建立鬆散耦合代碼的技術。對象能夠發佈事件,用來表示在該對象生命週期中某個有趣的時刻到了。而後其餘對象能夠觀察該對象,等待這些有趣的時刻到來並經過運行代碼來響應。
觀察者模式由兩類對象組成:主體和觀察者。主體負責發佈事件,同時觀察者經過訂閱這些事件來觀察該主體。該模式的一個關鍵概念是主體並不知道觀察者的任何事情,也就是說它能夠獨自存在並正常運做即便觀察者不存在。從另外一方面來講,觀察者知道主體並能註冊事件的回調函數(事件處理程序)。涉及 DOM 上時, DOM 元素即是主體,你的事件處理代碼即是觀察者。
function EventTarget(){ this.handlers = {}; } EventTarget.prototype = { constructor: EventTarget, addHandler: function(type, handler){ if (typeof this.handlers[type] == "undefined"){ this.handlers[type] = []; } this.handlers[type].push(handler); }, fire: function(event){ if (!event.target){ event.target = this; } if (this.handlers[event.type] instanceof Array){ var handlers = this.handlers[event.type]; for (var i=0, len=handlers.length; i < len; i++){ handlers[i](event); } } }, removeHandler: function(type, handler){ if (this.handlers[type] instanceof Array){ var handlers = this.handlers[type]; for (var i=0, len=handlers.length; i < len; i++){ if (handlers[i] === handler){ break; } } handlers.splice(i, 1); } } }; function handleMessage(event){ alert("Message received: " + event.message); } //建立一個新對象 var target = new EventTarget(); //添加一個事件處理程序 target.addHandler("message", handleMessage); //觸發事件 target.fire({ type: "message", message: "Hello world!"}); //刪除事件處理程序 target.removeHandler("message", handleMessage); //再次,應沒有處理程序 target.fire({ type: "message", message: "Hello world!"});
使用 DragEvent - Web APIs | MDN 或者使用鼠標事件進行模擬。
支持離線 Web 應用開發是 HTML5 的另外一個重點。
開發離線 Web 應用:
經過navigator.onLine
屬性檢測是否離線。
在線離線狀態切換時會觸發 window 的 online 和 offline 事件。
書中的方法已經廢棄!!!如今應該這樣實現:經過 Service workers 讓 PWA 離線工做 - 漸進式 Web 應用(PWA) | MDN
使用描述文件(manifest file)例如出要下載和緩存的資源。
能夠在<html>
標籤的 manifest 屬性中指定這個文件的路徑,例如<html mainfest="/offline.manifest">
。
cookies.Cookie - Mozilla | MDN
IndexedDB 是一個數據庫,和 MySQL 等數據庫相似。
IndexedDB 最大的特色是使用對象保存數據,而不是表來保存數據。(和 MongoDB 有點像)
一個 IndexedDB 數據庫,就是一組位於相同命名空間下的對象的集合。
IndexedDB API - Web APIs | MDN
留坑。。
編寫可維護的代碼很重要,由於大部分開發人員都花費大量時間維護他人代碼。
數據量大的循環能夠考慮:Duff's device
最小化現場更新:經過文檔片斷(fragment)將 DOM 一塊兒更新。
一旦你須要訪問的 DOM 部分是已經顯示的頁面的一部分,那麼你就是在進行一個現場更新。之因此叫現場更新,是由於須要當即(現場)對頁面對用戶的顯示進行更新。每個更改,無論是插入單個字符,仍是移除整個片斷,都有一個性能懲罰,由於瀏覽器要從新計算無數尺寸以進行更新。現場更新進行得越多,代碼完成執行所花的時間就越長;完成一個操做所需的現場更新越少,代碼就越快。
使用 innerHTML:對於大量 DOM 更改,innerHTML 比 DOM 方法快。
有兩種在頁面上建立 DOM 節點的方法:使用諸如 createElement() 和 appendChild() 之類的 DOM 方法,以及使用 innerHTML。對於小的 DOM 更改而言,兩種方法效率都差很少。然而,對於大的 DOM 更改,使用 innerHTML 要比使用標準 DOM 方法建立一樣的 DOM 結構快得多。
使用事件代理:將事件處理程序附加到更高層的地方負責多個目標的事件處理。
大多數 Web 應用在用戶交互上大量用到事件處理程序。頁面上的事件處理程序的數量和頁面響應用戶交互的速度之間有個負相關。爲了減輕這種懲罰,最好使用事件代理。
注意 HTMLCollection
HTMLCollection 對象的陷阱已經在本書中討論過了,由於它們對於 Web 應用的性能而言是巨大的損害。記住,任什麼時候候要訪問 HTMLCollection,無論它是一個屬性仍是一個方法,都是在文檔上進行一個查詢,這個查詢開銷很昂貴。最小化訪問 HTMLCollection 的次數能夠極大地改進腳本的性能。
軟件開發典型模式:寫代碼 -> 編譯 -> 測試
JS 是非編譯型語言,模式變成:寫代碼 -> (語法轉換) -> 測試
如今一般是使用 ES6 和更新的語法,一些語法測試和生產環境還不支持,須要用進行語法轉換(通常使用 Babe)。
XXLint(常見的有 JSLint、TSLint、ESLint) 能夠查找代碼中的語法錯誤以及常見的編碼錯誤。
當談及 JavaScript 文件壓縮,其實在討論兩個東西:代碼長度和配重(Wire weight)。代碼長度指的是瀏覽器所需解析的字節數,配重指的是實際從服務器傳送到瀏覽器的字節數。在 Web 開發的早期,這兩個數字幾乎是同樣的,由於從服務器端到客戶端原封不動地傳遞了源文件。而在今天的 Web 上,這二者不多相等,實際上也不該相等。
HTTP 壓縮
配重指的是實際從服務器傳送到瀏覽器的字節數。由於如今的服務器和瀏覽器都有壓縮功能,這個字節數不必定和代碼長度同樣。全部的五大 Web 瀏覽器(IE、 Firefox、 Safari、 Chrome 和 Opera)都支持對所接收的資源進行客戶端解壓縮。這樣服務器端就可使用服務器端相關功能來壓縮 JavaScript 文件。一個指定了文件使用了給定格式進行了壓縮的 HTTP 頭包含在了服務器響應中。接着瀏覽器會查看該 HTTP 頭肯定文件是否已被壓縮,而後使用合適的格式進行解壓縮。結果是和原來的代碼量相比在網絡中傳遞的字節數量大大減小了。
<style> div { width: 200px; height: 20px; } #bar { border: 1px solid #000; position: relative; background-color: #666; overflow: hidden; } #progress { position: absolute; background-color: #fff; top: 0; left: 0; } </style> <div id="bar"> <div id="progress"></div> </div> <script> var start = null; var element = document.getElementById("progress"); element.style.position = "absolute"; function step(timestamp) { if (!start) start = timestamp; var progress = timestamp - start; element.style.left = Math.min(progress / 10, 200) + "px"; if (progress < 2000) { window.requestAnimationFrame(step); } } window.requestAnimationFrame(step); </script>
不知道用戶是否是正在與頁面交互,這是困擾廣大 Web 開發人員的一個主要問題。若是頁面最小化了或者隱藏在了其餘標籤頁後面,那麼有些功能是能夠停下來的,好比輪詢服務器或者某些動畫效果。而 Page Visibility API(頁面可見性 API)就是爲了讓開發人員知道頁面是否對用戶可見而推出的。
document.hidden
:頁面是否隱藏document.visibilityState
:表示當前頁面的可視狀態地理定位(geolocation)是最使人興奮,並且獲得了普遍支持的一個新 API。 經過這套 API, JavaScript 代碼可以訪問到用戶的當前位置信息。固然,訪問以前必須獲得用戶的明確許可,即贊成在頁面中共享其位置信息。若是頁面嘗試訪問地理定位信息,瀏覽器就會顯示一個對話框,請求用戶許可共享其位置信息。
使用navigator.geolocation
對象
不能直接訪問用戶計算機中的文件,一直都是 Web 應用開發中的一大障礙。 2000 年之前,處理文件的惟一方式就是在表單中加入
<input type="file">
字段,僅此而已。 File API(文件 API)的宗旨是爲 Web 開發人員提供一種安全的方式,以便在客戶端訪問用戶計算機中的文件,並更好地對這些文件執行操做。支持 File API 的瀏覽器有 IE10+、 Firefox 4+、 Safari 5.0.5+、 Opera 11.1 + 和 Chrome。
HTML5 在 DOM 中爲文件輸入元素添加了一個 files 集合,裏面包含着一組帶有文件信息的 File 對象。
FileReader 類型實現的是一種異步文件讀取機制。能夠把 FileReader 想象成 XMLHttpRequest,區別只是它讀取的是文件系統,而不是遠程服務器。
調用 blob 的 slice() 方法返回一個 Blob 實例,而後使用 FileReader 讀取這個 Blob 實例。
只讀文件的一部分能夠節省時間,適合只關注文件特定部分的狀況。
對象 URL 也被稱爲 blob URL,指的是引用保存在 File 或 Blob 中數據的 URL。使用對象 URL 的好處是能夠沒必要把文件內容讀取到 JavaScript 中而直接使用文件內容。爲此,只要在須要文件內容的地方提供對象 URL 便可。要建立對象 URL,可使用
window.URL.createObjectURL()
方法,並傳入 File 或 Blob 對象。
注意:要在不須要某個對象 URL 是手動釋放內存。
經過 event.dataTransfer.files
獲取文件信息。
可使用 File API 讀取文件內容而後 post 給後端(這樣後端收到的是問價的內容,還要再將她們保存),但更方便的作法是以提交表單的方式上傳文件(這樣後端就像接收到常規表單同樣處理就行):
append()
方法添加文件send()
方法發送 FormData 對象Performance:用於查看頁面加載的各個階段的時間(13 位的時間戳,精確到毫秒)
隨着 Web 應用複雜性的與日俱增,愈來愈複雜的計算在所不免。長時間運行的 JavaScript 進程會致使瀏覽器凍結用戶界面,讓人感受屏幕 「凍結」 了。 Web Workers 規範經過讓 JavaScript 在後臺運行解決了這個問題。瀏覽器實現 Web Workers 規範的方式有不少種,可使用線程、後臺進程或者運行在其餘處理器核心上的進程,等等。具體的實現細節其實沒有那麼重要,重要的是開發人員如今能夠放心地運行 JavaScript,而沒必要擔憂會影響用戶體驗了。
很是消耗時間的操做轉交給 Worker 就不會阻塞用戶界面了,例如:使用 Worker 排序數組 web worker example - CodeSandbox。
Worker 可使用importScripts()
方法導入其餘腳本。
Worker 分爲專用 Worker(dedicated worker)和共享 Worker(shared worker),前者不能在頁面間共享,後者能夠在多個窗口,iframe 或 Worker 間共享。
書中好多特性(例如,迭代器對象、數組領悟等)都沒有歸入 ES6 標準。
Proxy 對象用於定義基本操做的自定義行爲(如屬性查找,賦值,枚舉,函數調用等)。
另外一種描述:Proxy 對象能夠處理(捕捉)原生功能,並用本身的函數處理。
用普通對象保存鍵值對融合與原生屬性混淆,使用Map
類型能夠避免混淆。
WeakMap
是 ES 中惟一一個可以讓你知道對象何時已經徹底解除引用的類型。例:Babel 將類的私用屬性轉換爲 WeakMap,這樣私有屬性就不會強引用實例化的對象(強引用實例化的對象會致使對象內存沒法釋放)。
class Test { #t1 = 500 #t2 = 600 } // 轉換後 var Test = function Test() { _classCallCheck(this, Test); _t.set(this, { writable: true, value: 500 }); _t2.set(this, { writable: true, value: 600 }); }; var _t = new WeakMap(); var _t2 = new WeakMap();
嚴格模式下不容許:
delete
操做符嚴格模式下錯誤地操做對象更容易報錯(如,爲只讀屬性賦值等)。
嚴格模式下不能使用arguments.caller
和arguments.callee
this
非嚴格模式下使用函數的call()
和apply()
方法時null
和undefined
會轉爲爲全局對象(這很是危險)。
嚴格模式下this
就是制定的值
function C1(a){ this.val1 = a; } C1.call(null, 123);// globalThis.val1 變爲 123 function C2(a){ "use strict" this.val2 = a; } C.call(null, 456);// 報錯
ADsafe 和 Caja