JS高级技巧学习小结
安全类型检測
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高级技巧学习小结的更多相关文章
- 本周JavaScript学习小结
应组长杨老师号召,写个js阶段性学习小结. emmm这周学了Linux进程通讯,学正则表达式尝试完成第一次编程作业,中秋还去平潭露营(所以...js学得很少hhh). 现在还处于感性认识阶段,浏览了一 ...
- ExtJs学习笔记之学习小结LoginDemo
ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...
- 【转载】Hyperledger学习小结
Hyperledger学习小结 自学Hyperledger Composer也有段时间了,是时候对所学的知识总结一下了.因为没有实际项目参与的话,差不多也就到此为止了.后续可能会去了解一下以太坊的技术 ...
- AJAX学习小结
12345678910 $.ajax({ "url":"", //访问路径 "data":"", // 需要传输的数据 ...
- js面向对象学习 - 对象概念及创建对象
原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数 ...
- js数组学习整理
原文地址:js数组学习整理 常用的js数组操作方法及原理 1.声明数组的方式 var colors = new Array();//空的数组 var colors = new Array(3); // ...
- js入门学习~ 运动应用小例
要实现的效果如下: 鼠标移入各个小方块,实现对应的效果(变宽,变高,移入透明,移出恢复)~~ (且各运动相互之前不干扰) 主要是练习多个物体的运动框架~~ --------------------- ...
- flex学习小结
接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...
- Python 学习小结
python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...
随机推荐
- 如何让一个div里面的div垂直居中?
如何让一个div里面的div垂直居中? 如何让上面灰色有文字那个div和背景图标垂直居中,不管屏幕大小有好大,始终在垂直方向上的中间.上面有整个布局和样式表,谢谢高手指点 CSS3时代当然要用CSS3 ...
- (转) 淘淘商城系列——Redis的安装
http://blog.csdn.net/yerenyuan_pku/article/details/72849612 通过上文的学习,我相信大家已经将首页的轮播图展示出来了,接下来我们将进入一个新的 ...
- 如何使用crash分析vmcore - 之基础思路case1
如何使用crash分析vmcore - 之基础思路case1 dmesg查看内核日志 [2493382.671020] systemd-shutdown[1]: Sending SIGKILL to ...
- Spring Boot 与ElasticSearch
一.ElasticSearch 介绍 开源的 ElasticSearch 是目前全文搜索引擎的首选,它是一个分布式搜索服务,提供Restful API,它可以快速地存储.搜索和分析海量数据.底层基 ...
- PS切图基本操作
PS切图基本操作 2016-05-11 20:56:46| 分类: PhotoShop|字号 订阅 下载LOFTER我的照片书 | 1首先在“文件”中打开一张图片. 2点击“移 ...
- Gym - 101670J Punching Power(CTU Open Contest 2017 最大独立集)
题目: The park management finally decided to install some popular boxing machines at various strategic ...
- HDU - 6158 The Designer
传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6158 本题是一个计算几何题——四圆相切. 平面上的一对内切圆,半径分别为R和r.现在这一对内切圆之间,按 ...
- 【19】AngularJS 应用
AngularJS 应用 现在是时候创建一个真正的 AngularJS 单页 Web 应用(single page web application,SPA)了. AngularJS 应用实例 现在可以 ...
- mybatis example 排序 语句
mybatis example 排序 语句 IntegralInfoExample integral = new IntegralInfoExample(); integral.createCrite ...
- CentOS服务器上部署 oracle10gr2
1.下载Centos系统 Linux 镜像文件. 推荐使用 CentOS5.4,下载地址:http://isoredirect.centos.org/centos/5/isos/i38 ...