安全类型检測

var isArray = value instanceof Array;

以上代码要返回true,value必须是一个数组,并且还必须与Array构造函数在同一个全局作用域中(Array是window的属性)。

假设value是在还有一个框架中定义的数组。那么以上代码就会返回false.

Demo:

<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.html

<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实例环境中调用构造函数。

不论什么一种情况。对象初始化都能够正常进行。

假设this对象不是Person的实例。那么会再次使用new操作符调用构造函数并返回结果。

这样就能够确保不管是否使用new操作符,都会返回一个Person的新实例。

Demo1:

<!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>

JS高级技巧学习小结的更多相关文章

  1. 本周JavaScript学习小结

    应组长杨老师号召,写个js阶段性学习小结. emmm这周学了Linux进程通讯,学正则表达式尝试完成第一次编程作业,中秋还去平潭露营(所以...js学得很少hhh). 现在还处于感性认识阶段,浏览了一 ...

  2. ExtJs学习笔记之学习小结LoginDemo

    ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  3. 【转载】Hyperledger学习小结

    Hyperledger学习小结 自学Hyperledger Composer也有段时间了,是时候对所学的知识总结一下了.因为没有实际项目参与的话,差不多也就到此为止了.后续可能会去了解一下以太坊的技术 ...

  4. AJAX学习小结

    12345678910 $.ajax({ "url":"", //访问路径 "data":"", // 需要传输的数据 ...

  5. js面向对象学习 - 对象概念及创建对象

    原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数 ...

  6. js数组学习整理

    原文地址:js数组学习整理 常用的js数组操作方法及原理 1.声明数组的方式 var colors = new Array();//空的数组 var colors = new Array(3); // ...

  7. js入门学习~ 运动应用小例

    要实现的效果如下: 鼠标移入各个小方块,实现对应的效果(变宽,变高,移入透明,移出恢复)~~ (且各运动相互之前不干扰)  主要是练习多个物体的运动框架~~ --------------------- ...

  8. flex学习小结

    接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...

  9. Python 学习小结

    python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...

随机推荐

  1. 如何让一个div里面的div垂直居中?

    如何让一个div里面的div垂直居中? 如何让上面灰色有文字那个div和背景图标垂直居中,不管屏幕大小有好大,始终在垂直方向上的中间.上面有整个布局和样式表,谢谢高手指点 CSS3时代当然要用CSS3 ...

  2. (转) 淘淘商城系列——Redis的安装

    http://blog.csdn.net/yerenyuan_pku/article/details/72849612 通过上文的学习,我相信大家已经将首页的轮播图展示出来了,接下来我们将进入一个新的 ...

  3. 如何使用crash分析vmcore - 之基础思路case1

    如何使用crash分析vmcore - 之基础思路case1 dmesg查看内核日志 [2493382.671020] systemd-shutdown[1]: Sending SIGKILL to ...

  4. Spring Boot 与ElasticSearch

    一.ElasticSearch 介绍 ​ 开源的 ElasticSearch 是目前全文搜索引擎的首选,它是一个分布式搜索服务,提供Restful API,它可以快速地存储.搜索和分析海量数据.底层基 ...

  5. PS切图基本操作

    PS切图基本操作 2016-05-11 20:56:46|  分类: PhotoShop|字号 订阅     下载LOFTER我的照片书  |     1首先在“文件”中打开一张图片.   2点击“移 ...

  6. Gym - 101670J Punching Power(CTU Open Contest 2017 最大独立集)

    题目: The park management finally decided to install some popular boxing machines at various strategic ...

  7. HDU - 6158 The Designer

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6158 本题是一个计算几何题——四圆相切. 平面上的一对内切圆,半径分别为R和r.现在这一对内切圆之间,按 ...

  8. 【19】AngularJS 应用

    AngularJS 应用 现在是时候创建一个真正的 AngularJS 单页 Web 应用(single page web application,SPA)了. AngularJS 应用实例 现在可以 ...

  9. mybatis example 排序 语句

    mybatis example 排序 语句 IntegralInfoExample integral = new IntegralInfoExample(); integral.createCrite ...

  10. CentOS服务器上部署 oracle10gr2

    1.下载Centos系统 Linux 镜像文件.         推荐使用 CentOS5.4,下载地址:http://isoredirect.centos.org/centos/5/isos/i38 ...