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>
相关文章
相关标签/搜索