JS高級技巧學習小結

安全類型檢測

var isArray = value instanceof Array;

以上代碼要返回true,value必須是一個數組,而且還必須與Array構造函數在同一個全局做用域中(Array是window的屬性)。javascript

假設value是在還有一個框架中定義的數組。那麼以上代碼就會返回false.
Demo:html

<body>
<iframe src="test.html" id="myIframe"></iframe>
<script type="text/javascript"> window.onload = function(){ var oFrame = document.getElementById("myIframe"); var res = oFrame.contentWindow.sayArr(); console.log(res instanceof Array);//false console.log(res);//[1,2,3] } </script>
</body>

test.htmljava

<script type="text/javascript"> var isArray = value instanceof Array; var arr = [1,2,3]; function sayArr(){ console.log(arr instanceof Array);//true return arr; } </script>

咱們知道,在不論什麼值上調用Object原生的toString()方法,都會返回一個[object NativeConstructorName]格式的字符串。設計模式

每個類在內部都有一個[[Class]]屬性。這個屬性中就指定了上述字符串中的構造函數名。
舉個樣例:數組

console.log(Object.prototype.toString.call(123));//[object Number]

因爲原生數組的構造函數名與全局做用域無關,所以使用toString()就能保證返回一致的值。所以,咱們可以經過如下函數來進行推斷。瀏覽器

<script type="text/javascript"> //推斷某個值是否是原生數組 function isArray(value){ return Object.prototype.toString.call(value)=="[object Array]"; } //推斷某個值是否是原生函數 function isFunction(value){ return Object.prototype.toString.call(value)=="[object Function]"; } //推斷某個值是否是原生正則表達式 function isRegExp(value){ return Object.prototype.toString.call(value)=="[object RegExp]"; } </script>

做用域安全的構造函數

構造函數事實上就是一個使用new操做符調用的函數。當使用new調用時,構造函數內部用到的this對象會指向新建立的對象實例安全

Person構造函數加入了一個檢查並確保this對象是Person實例的if語句,它要麼使用new操做符。要麼在現有的Person實例環境中調用構造函數。markdown

不論什麼一種狀況。對象初始化都可以正常進行。
假設this對象不是Person的實例。那麼會再次使用new操做符調用構造函數並返回結果。閉包

這樣就可以確保不論是否使用new操做符,都會返回一個Person的新實例。
Demo1:app

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>做用域安全的構造函數</title>
    </head>
    <body>
    <script type="text/javascript"> function Person(name,age,job){ if(this instanceof Person)//這裏檢測以確保this是Person的實例 { this.name=name; this.age=age; this.job=job; }else{ return new Person(name,age,job); } } var person1=Person("liujie",23,"master"); console.log(window.name);//"" console.log(person1.name);//liujie var person2=new Person("lisi",21,"student"); console.log(person2.name);//lisi </script>
    </body>
</html>

Demo2

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>做用域安全的構造函數</title>
</head>
<body>
    <script type="text/javascript"> function Person(name, age, job){ this.name = name; this.age = age; this.job = job; } var person1 = new Person("Nicholas", 29, "Software Engineer"); console.log(person1);//[object Object] console.log(person1.name); //"Nicholas" console.log(person1.age); //29 console.log(person1.job); //"Software Engineer" var person2 = Person("Nicholas", 29, "Software Engineer"); //這裏忽略了new操做符,把構造函數做爲普通函數調用 console.log(person2); //undefined 因爲Person函數沒有返回值 console.log(window.name); //"Nicholas" 這裏this-->window console.log(window.age); //29 console.log(window.job); //"Software Engineer" </script>
</body>
</html>

特別注意:這裏問題在於沒有使用new操做符來調用該構造函數的狀況上,因爲該this對象是在運行時綁定的,因此直接調用Person(),this會映射到全局對象window上,致使錯誤對象屬性的意外添加。

這裏本來針對Person實例的三個屬性被加到window對象上,因爲構造函數是做爲普通函數調用的。忽略了new操做符。這個問題是因爲this對象的晚綁定形成的,在這裏this被解析成了window對象。因爲window的name屬性是用於識別連接目標和frame的,因此這裏對該屬性的偶然覆蓋可能會致使該頁面上出現其它錯誤。可以建立一個做用域安全的構造函數來解決問題。
Demo3
在實現了做用域安全的構造函數後,假設使用構造函數竊取模式的繼承(在子類中調用父類的構造函數,經過這樣的方式給子類加入屬性和方法)且不使用原型鏈,那麼這個繼承可能被破壞。
如下的代碼,Polygon構造函數是做用域安全的,然而Rectangle構造函數則不是。新建立一個Rectangle實例後,這個實例應該經過Polygon.call()來繼承Polygon的sides屬性。但是。因爲Polygon構造函數是做用域安全的,this對象並非Polygon的實例。因此會建立並返回一個新的Polygon對象。Rectangle構造函數中的this對象並無獲得增加,同一時候Polygon.call()返回的值也沒實用到。因此Rectangle實例中就不會有sides屬性。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>做用域安全的構造函數</title>
</head>
<body>
    <script type="text/javascript"> function Polygon(sides){ if (this instanceof Polygon) { this.sides = sides; this.getArea = function(){ return 0; }; } else { return new Polygon(sides); } } function Rectangle(width, height){ Polygon.call(this, 2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } var rect = new Rectangle(5, 10); console.log(rect.sides); //undefined </script>
</body>
</html>

Demo4
構造函數竊取結合使用原型鏈可以解決問題
這樣一來,一個Rectangle實例也同一時候是一個Polygon實例,因此Polygon.call()會照原意運行,終於爲Rectangle實例加入sides屬性。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>做用域安全的構造函數</title>
</head>
<body>
    <script type="text/javascript"> function Polygon(sides){ if (this instanceof Polygon) { this.sides = sides; this.getArea = function(){ return 0; }; } else { return new Polygon(sides); } } function Rectangle(width, height){ Polygon.call(this, 2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } Rectangle.prototype=new Polygon();//實現繼承 var rect = new Rectangle(5, 10); console.log(rect.sides); //2 </script>
</body>
</html>

推薦做用域安全的構造函數做爲最佳實踐。

惰性加載函數

惰性加載表示函數運行的分支僅會發生一次
有兩種實現惰性加載的方式,第一種就是在函數被調用時再處理函數。

在第一次調用的過程當中,該函數會被覆蓋爲還有一個按合適方式運行的函數,這樣不論什麼對原函數的調用都不用再通過運行的分支了。

在這個惰性加載的createXHR()中,if語句的每個分支都會爲createXHR變量賦值有效覆蓋了原有的函數。最後一步即是調用新賦的函數。

下一次調用createXHR()的時候。就會直接調用被分配的函數,這樣就不需要再次運行if語句了

這就是惰性加載的第一種核心思想。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>惰性加載函數</title>
</head>
<body>
<script type="text/javascript"> function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ createXHR = function(){ return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != "undefined"){ createXHR = function(){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; } catch (ex){ //skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { createXHR = function(){ throw new Error("No XHR object available."); }; } return createXHR(); } var xhr1 = createXHR(); var xhr2 = createXHR(); </script>
</body>
</html>

另一種實現方式:在聲明函數時就指定適當的函數。
這樣,第一次調用函數時就不會損失性能了,而在代碼首次加載時會損失一點性能(因爲首次加載需要通過每個if分支來肯定使用哪個函數聲明更好)
這個樣例的技巧:建立了一個自運行的匿名函數,用以肯定應該使用哪個函數實現。每個分支都返回正確的函數定義。以便立刻將其賦值給createXHR()。這樣咱們在第一次調用createXHR()的時候。就直接使用的是最佳的函數聲明,不會再走if分支推斷了。

這樣的惰性加載函數的優勢是:僅僅在運行分支代碼時犧牲一點性能

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>惰性加載函數</title>
</head>
<body>
<script type="text/javascript"> var createXHR = (function(){ if (typeof XMLHttpRequest != "undefined"){ return function(){ return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != "undefined"){ return function(){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ //skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { return function(){ throw new Error("No XHR object available."); }; } })(); var xhr1 = createXHR(); var xhr2 = createXHR(); </script>
</body>
</html>

函數綁定

函數綁定要建立一個函數,可以在特定的this環境中以指定參數調用還有一個函數。該技巧常常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同一時候保留代碼運行環境。


js庫實現了一個可以將函數綁定到指定環境的函數–bind()
bind()函數接收一個函數和一個環境,並返回一個在給定環境中調用給定函數的函數,而且將所有參數原封不動傳遞過去。
在bind()函數中建立了一個閉包,閉包使用apply()調用傳入的函數,並給apply()傳遞context對象和參數。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數綁定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript"> function bind(fn, context){//接收一個函數和一個環境 return function(){ return fn.apply(context, arguments); }; } var handler = { message: "Event handled", handleClick: function(event){ console.log(this.message + ":" + event.type);//Event handled:click } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler)); </script>
</body>
</html>

函數綁定要建立一個函數。可以在特定的this環境中以指定參數調用還有一個函數。

該技巧常常和回調函數與事件處理程序一塊兒使用。以便在將函數做爲變量傳遞的同一時候保留代碼運行環境。

如下的樣例將對象handler的handleClick方法分配爲按鈕的事件處理程序。當按下按鈕時,就應該調用該函數。顯示一個警告框。儘管貌似警告框應該顯示Event handled,然而實際上顯示undefined。這是因爲沒有保存handler.handleClick()的運行環境。因此this指向了DOM按鈕而不是handler對象。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數綁定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript"> var handler = { message: "Event handled", handleClick: function(event){ //console.log(this);//<input id="my-btn" type="button" value="Click Me"> console.log(this.message);//undefined } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick); </script>
</body>
</html>

函數綁定要建立一個函數。可以在特定的this環境中以指定參數調用還有一個函數。

該技巧常常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同一時候保留代碼運行環境。

如下的樣例將對象handler的handleClick方法分配爲按鈕的事件處理程序。

當按下按鈕時,就應該調用該函數,顯示一個警告框。

儘管貌似警告框應該顯示Event handled,然而實際上顯示undefined。

這是因爲沒有保存handler.handleClick()的運行環境,因此this指向了DOM按鈕而不是handler對象。

這裏在onclick事件處理程序中使用了一個閉包直接調用handler.handleClick()。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數綁定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript"> var handler = { message: "Event handled", handleClick: function(event){ //console.log(this);// Object { message="Event handled", handleClick=function()} console.log(this.message);//Event handled } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", function(event){ handler.handleClick(event); }); </script>
</body>
</html>

ECMAScript5爲所有函數定義了一個原生的bind()方法。進一步簡單了操做。
無論原生的bind方法仍是本身定義的bind方法,都需要傳入做爲this值的對象

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>bind函數綁定</title>
</head>
<body>
<input type="button" id="my-btn" value="Click Me" />
<script type="text/javascript" src="EventUtil.js"></script>
    <script type="text/javascript"> var handler = { message: "Event handled", handleClick: function(event){ console.log(this.message + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler)); </script>
</body>
</html>

函數柯里化

柯里化是一種贊成使用部分函數參數構造函數的方式。也就是意味着,你在調用一個函數時,可以傳入需要的所有參數並得到返回結果。也可以傳入部分參數並的獲得一個返回的函數,這個返回的函數需要傳入的就是其他的參數。
Demo:

<script type="text/javascript"> function curry(fn){ var args = Array.prototype.slice.call(arguments,1); return function(){//這裏使用了閉包 var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(null,finalArgs); } } function add(num1,num2,num3){ return num1+num2+num3; } var curriedAdd = curry(add,4); var res = curriedAdd(5,6); console.log(res);//15 </script>

上面這個樣例中,add函數就是要柯里化的函數,其僅僅傳入了一個參數4。這個函數返回了一個柯里化的函數。這個柯里化的函數接收剩餘的參數。


我的理解:* 這裏curriedAdd就是函數add第一個參數爲4的柯里化版本號。*
函數柯里化–用於建立已經設置好了一個或多個參數的函數

其基本方法與函數綁定同樣:使用一個閉包返回一個函數。二者差異在於:函數柯里化在函數被調用時。返回的函數還需要傳入參數。

建立方法:調用還有一個函數併爲它傳入要柯里化的函數和必要參數
Demo1

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數柯里化</title>
</head>
<body>
    <script type="text/javascript"> function curry(fn){ //這裏在arguments對象上調用了slice()方法,並傳入參數1表示被返回的數組包括從第二個參數開始的所有參數 var args = Array.prototype.slice.call(arguments, 1);//slice() 方法可從已有的數組中返回選定的元素。 return function(){ var innerArgs = Array.prototype.slice.call(arguments),//innerArgs表示內部函數的參數數組 finalArgs = args.concat(innerArgs);//將外部函數參數數組和內部函數參數數組進行鏈接 //concat()鏈接兩個或不少其它的數組,並返回結果。 return fn.apply(null, finalArgs); //這裏的null表示沒有考慮fn函數的運行環境 //這樣一來this指向Global,而在瀏覽器環境下。Global就是window }; } function add(num1, num2){//求和函數 return num1 + num2; } //curry()函數的第一個參數是要柯里化的函數,其它參數是要傳入的值 var curriedAdd = curry(add, 5);//這裏的5是外部函數參數,3是內部函數參數 alert(curriedAdd(3)); //8 var curriedAdd2 = curry(add, 5, 12);//柯里化的add函數 alert(curriedAdd2()); //17 </script>
</body>
</html>

Demo2
函數柯里化還常常做爲函數綁定的一部分包括在當中,構造出更爲複雜的bind函數。
這裏bind同一時候接受函數和一個object對象。表示給被綁定的函數的參數是從第三個開始的。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數柯里化</title>
</head>
<body>
    <input type="button" id="my-btn" value="Click Me">
    <script type="text/javascript" src="../EventUtil.js"></script>
    <script type="text/javascript"> function bind(fn, context){//fn=handler.handleClick context=handler var args = Array.prototype.slice.call(arguments, 2);//獲取到"my-btn" return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(context, finalArgs);//handler.handleClick.apply(handler, "my-btn"); }; } var handler = { message: "Event handled", handleClick: function(name, event){//name是要處理的元素的名字 //event就是event對象 console.log(this.message + ":" + name + ":" + event.type);//Event handled:my-btn:click } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn")); </script>
</body>
</html>

Demo3

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
    <title>函數柯里化</title>
</head>
<body>
    <input type="button" id="my-btn" value="Click Me">
    <script type="text/javascript" src="../EventUtil.js"></script>
    <script type="text/javascript"> var handler = { message: "Event handled", handleClick: function(name, event){ console.log(this.message + ":" + name + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn")); //handler.handleClick.bind(handler, "my-btn")這樣綁定指handler.handleClick函數中的this指向handler對象 //bind()方法也實現了函數柯里化,僅僅要在this的值以後再傳入還有一個參數就能夠 </script>
</body>
</html>

防纂改對象

不可擴展對象

Object.preventExtensions()方法用來阻止給對象加入屬性和方法。但是已有的成員絲絕不受影響,你仍然可以改動和刪除已有的成員。

<script type="text/javascript"> var person = { name: "Nicholas" }; Object.preventExtensions(person); person.age = 29; console.log(person.age);//undefined </script>
<script type="text/javascript"> /* Object.preventExtensions()用來阻止給對象加入屬性和方法 Object.isExtensible()方法用來推斷元素可否夠擴展 */ var person = { name: "Nicholas" }; console.log(Object.isExtensible(person)); //true Object.preventExtensions(person); console.log(Object.isExtensible(person)); //false person.age = 29; console.log(person.age);//undefined </script>

密封的對象

密封對象不可擴展。而且已有成員的[[Configurable]]特性將被設置爲false。這就意味着不能刪除屬性和方法,但屬性值是可以改動的。

<script type="text/javascript"> /* Object.seal()將對象密封,不能給對象加入和刪除屬性和方法 */ var person = { name: "Nicholas" }; Object.seal(person); person.age = 29; console.log(person.age); //undefined //表示可以改動屬性值 person.name = "liss"; console.log(person.name);//liss delete person.name; console.log(person.name); //"Nicholas" </script>
<script type="text/javascript"> var person = { name: "Nicholas" }; console.log(Object.isExtensible(person)); //true 返回true表示對象可以擴展 console.log(Object.isSealed(person)); //false 返回false表示對象沒有密封 Object.seal(person); //密封的對象不可擴展,因此這裏返回false console.log(Object.isExtensible(person)); //false console.log(Object.isSealed(person)); //true 對象被密封 person.age = 29; console.log(person.age);//undefined" </script>

凍結的對象

<script type="text/javascript">
/*
Object.freeze()方法是凍結對象,凍結的對象既不能擴展,同一時候也是密封的。而且對象的[[writeable]]特性也被設置爲false
 */
    var person = { name: "Nicholas" };
        Object.freeze(person);

        person.age = 29;//不可以擴展
        console.log(person.age);      //undefined

        delete person.name;//不可以刪除
        console.log(person.name);     //"Nicholas"

        person.name = "Greg";//因爲writeable]]特性被設置爲false的緣由,不能被改動
        console.log(person.name);     //"Nicholas"
</script>
<script type="text/javascript"> var person = { name: "Nicholas" }; console.log(Object.isExtensible(person)); //true console.log(Object.isSealed(person)); //false console.log(Object.isFrozen(person)); //false Object.isFrozen()用來推斷對象是否被凍結 Object.freeze(person); console.log(Object.isExtensible(person)); //false console.log(Object.isSealed(person)); //true console.log(Object.isFrozen(person)); //true person.age = 29; console.log(person.age);//undefined </script>

高級定時器

js是運行於單線程的環境中的,定時器僅僅僅僅是計劃代碼在將來的某個時間運行。運行時機是不能保證的,因爲在頁面的生命週期中。不一樣事件可能有其它代碼在控制js進程。在頁面下載完後的代碼運行、事件處理程序、Ajax回調函數都必須使用相同的線程來運行。

實際上,瀏覽器負責進行排序,指派某段代碼在某個時間點運行的優先級。

當某個按鈕被按下。它的事件處理程序代碼就會被加入到隊列中。並在下一個可能的時間裏運行。當接收到某個Ajax響應時,回調函數的代碼會被加入到隊列。在js中沒有不論什麼代碼時立馬運行的。但是一旦進程空暇則儘快運行。

定時器對隊列的工做方式是:當特定時間過去後將代碼插入。注意。給隊列加入代碼並不意味着對它立馬運行,而僅僅能表示它會盡快運行。好比:設定一個150ms後運行的定時器不表明到了150ms代碼就立馬運行。它表示代碼會在150ms後被加入到隊列中。假設在這個時間點,隊列中沒有其它東西,那麼這段代碼就會被運行,表面上看上去就好像代碼就在精確的時間點上運行了。其它狀況。代碼可能明顯等待更長時間才運行。
Demo1:
爲了不setInterval()的反覆定時器的缺點,可以採用鏈式setTimeout()方式
調用setTimeout(),每次函數運行的時候都會建立一個新的定時器。第二個setTimeout()調用使用了arguments.callee來獲取對當前運行的函數的引用,併爲其設置另一個定時器。這樣作的優勢:在前一個定時器代碼運行完以前,不會向隊列插入新的定時器代碼,確保不會有不論什麼缺失的間隔。

而且。它可以保證在下一次定時器代碼運行以前,至少要等待指定的間隔,避免了連續的運行。這個模式主要用於反覆定時器。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>反覆的定時器</title>
</head>
<body>
<div id="myDiv" style="position:absolute;width:100px;height:100px;left:0px;top:10px;background:red;"></div>
    <script type="text/javascript"> setTimeout(function() { var div = document.getElementById("myDiv"), left = parseInt(div.style.left) + 5; div.style.left = left + "px"; if (left < 200){ setTimeout(arguments.callee, 50); } }, 50); </script>
</body>
</html>

數組分塊技術

數組分塊技術主要的思路:爲要處理的項目建立一個隊列,而後使用定時器取出下一個要處理的項目進行處理,接着再設置還有一個定時器。
數組分塊的重要性在於它可以將多個項目的處理在運行隊列上分開,在每個項目處理以後,給予其它的瀏覽器處理機會運行。這樣就可能避免長時間運行腳本的錯誤。

data.concat():當不傳遞不論什麼參數調用數組的concat()方法時,將返回和原來數組中項目同樣的數組。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>數組分塊技術</title>
</head>
<body>
<div id="myDiv" style="background:red;"></div>
    <script type="text/javascript"> var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342]; function chunk(array, process, context){ //三個參數:要處理的項目的數組,用於處理項目的函數。可選的運行該函數的環境 setTimeout(function(){ var item = array.shift();//獲取隊列中下一個要處理的項目 process.call(context, item); if (array.length > 0){ setTimeout(arguments.callee, 100); } }, 100); } function printValue(item){ var div = document.getElementById("myDiv"); div.innerHTML += item + "<br>"; } chunk(data, printValue); </script>
</body>
</html>

函數節流

DOM操做比起非DOM交互需要不少其它的內存和CPU時間。連續嘗試進行過多的DOM相關操做可能會致使瀏覽器掛起,有時候甚至會崩潰。
假設在程序中使用了onresize事件處理程序。當調整瀏覽器大小的時候,該事件會連續觸發。

假設在該事件處理程序內部進行了相關DOM操做。其高頻率的更改可能會致使瀏覽器崩潰。爲了繞開這個問題,咱們可以考慮使用定時器對該函數進行節流。
函數節流背後的基本思想是:某些代碼不可以在沒有間斷的狀況下連續反覆運行。

第一次調用函數,建立一個定時器,在指定的時間間隔以後運行代碼。當第二次調用該函數時。它會清除以前的定時器並設置還有一個。假設前一個定時器已經運行過了,這個操做就沒有不論什麼意義。然而,假設前一個定時器還沒有運行,事實上就是將其替換爲一個新的定時器。目的是在僅僅有在運行函數的請求中止了一段時間以後才運行。

咱們使用throttle()函數來實現定時器的設置和清除


throttle()函數接收兩個參數:要運行的函數以及在哪一個做用域中運行。在函數中先清除以前設置的不論什麼定時器。定時器ID是存儲在函數的tId屬性中的。

僅僅要代碼是週期性運行的,都應該使用節流。但是你不能控制請求運行的速率。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>函數節流</title>
</head>
<body>
    <div id="myDiv" style="background:red;"></div>
    <script type="text/javascript"> function throttle(method, scope) { //下一次運行前,先清除上一次的定時器,控制處理的頻率 clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(scope); }, 100); } function resizeDiv(){ var div = document.getElementById("myDiv"); div.style.height = div.offsetWidth + "px"; } window.onresize = function(){ //調用節流函數進行處理頻率的控制 throttle(resizeDiv); }; </script>
</body>
</html>

節流在resize事件中是最常用的。假設你基於該事件來改變頁面佈局的話,最好控制處理的頻率,以確保瀏覽器不會在極短的時間內進行過多的計算。
在上面的樣例中有兩個問題可能會致使瀏覽器運行緩慢。

首先。要計算offsetWidth屬性,假設該元素或者頁面上其它元素有很是複雜的CSS樣式,那麼這個過程將會很是複雜。其次,設置某個元素的高度需要對頁面進行迴流來令改動生效。假設頁面有很是多元素同一時候應用了至關數量的CSS的話,這又需要很是多計算。

本身定義事件

事件是一種叫作觀察者的設計模式,這是一種建立鬆散耦合代碼的技術。對象可以公佈事件,用來表示在該對象生命週期中某個時刻到了。而後其它對象可以觀察該對象,等待這些時刻到來並經過運行代碼來響應。

觀察者模式由兩類對象組成:主體和觀察者。主體負責公佈事件,同一時候觀察者經過訂閱這些事件來觀察主體。該模式的一個關鍵概念是主體並不知道觀察者的不論什麼事情。也就是說它可以獨自存在並正常運做即便觀察者不存在。
Demo1

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>本身定義事件</title>
</head>
<body>
<div id="myDiv" style="background:red;"></div>
<script type="text/javascript" src="EventTarget.js"></script>
    <script type="text/javascript"> function handleMessage(event){ console.log("Message received: " + event.message);//Hello world! } //建立一個新對象 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!"}); </script>
</body>
</html>

EventTarget.js

function EventTarget(){//構造函數 this.handlers = {};//該屬性用來存儲事件處理程序 } EventTarget.prototype = {//原型對象 constructor: EventTarget, addHandler: function(type, handler){//該方法用於註冊給定類型事件的事件處理程序 //該方法接受兩個參數:事件類型和用於處理該事件的函數。

當調用該方法時。會進行一次檢查。 //看看handlers屬性中是否已經存在一個針對該事件類型的數組。假設沒有,則建立一個新的。 //而後使用push()方法將該處理程序加入到數組的末尾 if (typeof this.handlers[type] == "undefined"){ this.handlers[type] = []; } this.handlers[type].push(handler); }, fire: function(event){//該方法用於觸發一個事件 /*該方法接受一個單獨的參數。是一個至少包括type屬性的對象。

fire()方法先給event對象設置 一個target屬性,假設它還沒有被指定的話。而後它就查找相應事件類型的一組處理程序,調用各個函數, 並給出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操做符退出for循環。而後使用splice()方法將該項目從數組中刪除 break; } } handlers.splice(i, 1); } } };

Person類型使用寄生組合繼承方法來繼承EventTarget
Demo2

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>本身定義事件</title>
</head>
<body>
<div id="myDiv" style="background:red;"></div>
    <script type="text/javascript"> function object(o){ function F(){} F.prototype = o; return new F(); } function inheritPrototype(subType, superType){ var prototype = object(superType.prototype);//建立對象 prototype.constructor = subType;//加強對象 subType.prototype = prototype; //指定對象 } function Person(name, age){ EventTarget.call(this); this.name = name; this.age = age; } inheritPrototype(Person,EventTarget); Person.prototype.say = function(message){ this.fire({type: "message", message: message}); }; function handleMessage(event){ console.log(event.target.name + " says: " + event.message); } var person = new Person("Nicholas", 29); person.addHandler("message", handleMessage); person.say("Hi there."); </script>
</body>
</html>
相關文章
相關標籤/搜索