高级函数
类型检测
在任何值上调用Object的toString()
方法都会返回一个表示该对象原生构造函数名的字符串,因此可以使用该方法检测对象类型,弥补type of
以及 instanceof
的一些问题。
Object.prototype.toString.call(value) == "[object Array]"; //检测值的类型是否为数组
Object.prototype.toString.call(value) == "[object Function]"; //检测值的类型是否为函数
Object.prototype.toString.call(value) == "[object RegExp]"; //检测值的类型是否为正则表达式
开发人员定义的任何构造函数都将返回[object Object]
,因此可以用此方法,来区分Javascript原生和非原生对象
//检测是否原生的JSON对象
var isNativeJSON = window.JSON && Object.prototypr.toString.call(JSON) == "[object JSON]";
作用域安全的构造函数
当调用构造函数时,没有使用new
操作符时,属性将会被绑定到window对象上
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
}
var person = Person("zjw",29,"student");
alert(window.name); //"zjw"
alert(window.age); //29
alert(window.job); //"student"
可以在构造函数中首先确认this对象是否为正确类型的实例。如果不是则创建新的实例并返回
function Person(name,age,job){
if (this instanceof Person){
this.name = name;
this.age = age;
this.job = job;
}else{
return new Person(name,age,job);
}
}
惰性载入函数
多数JavaScript代码包含了大量的if
语句,例如一个使用多条if
分支解决不同浏览器兼容问题,从而执行某种功能的函数,往往第一次调用就能确认浏览器是否支持功能而采取某种行为,后续再次调用该函数就不需要再判断了。为了解决这个问题,可以使用惰性载入的技巧
第一种实现惰性载入的方法就是函数覆盖
function someFunction(){
if (condition1){
someFunction = function(){ //覆盖原函数
//do something
};
}else if (condition2){
someFunction = function(){ //覆盖原函数
//do another thing
};
}else{
someFunction = function(){ //覆盖原函数
//do another thing
};
}
return someFunction(); //返回覆盖后的函数执行结果
}
第二种方法是在声明函数时就指定适当的函数
var someFunction = (function(){
if (condition1){
return function(){ //覆盖原函数
//do something
};
}else if (condition2){
return function(){ //覆盖原函数
//do another thing
};
}else{
return function(){ //覆盖原函数
//do another thing
};
}
})();
函数绑定
函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。
var handler = {
message = "Event handle",
handleClick : function(event){
alert(this.message);
}
};
var btn = docuemnt.getElementById("my-btn");
btn.addEventListener("click",handler.handleClick,false);
上述代码给一个DOM按钮分配了一个事件处理程序,看似按下按钮后,将会弹出警告框,显示”Event handle”。实际上,显示的是undefined。this总是指向调用其的对象,此处传入的只是一个函数,调用函数时,this对象指向了DOM按钮,而不是handler对象。
可以使用闭包来修复这个问题
var handler = {
message = "Event handle",
handleClick : function(event){
alert(this.message);
}
};
var btn = docuemnt.getElementById("my-btn");
btn.addEventListener("click",function(event){
handler.handleClick(event); //此处用handler对象调用函数,因此this就会指向handler对象
},false);
在实际应用中,Javascript为所有函数提供了更方便的bind()
方法来解决这个问题,该方法接受1个参数,即要作为this值的对象
var handler = {
message = "Event handle",
handleClick : function(event){
alert(this.message);
}
};
var btn = docuemnt.getElementById("my-btn");
btn.addEventListener("click",handler.handleClick.bind(handler),false);
有关This对象的更多内容可以看看这两篇文章:
https://blog.csdn.net/zjw_python/article/details/80141312
https://blog.csdn.net/zjw_python/article/details/80141792
函数柯里化
用于创建已经设置好了一个参数或多个参数的函数。用通俗的话讲,比如有一个接受两个参数的函数,现在我要固定其中一个参数的值,让原本接受两个参数的函数变为只接受一个参数的函数,这样的过程就叫函数柯里化。
创建柯里化函数的基本方法是使用一个闭包返回一个函数,与函数绑定类似
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);
};
}
注意,这里的两个arguments对象不是同一个对象,第一个是包含了创建柯里化函数时传入的参数,第二个是包含了调用柯里化函数传入的参数。因此要将两次传入的参数一并传给原来的函数。而且由于没有考虑到执行环境,所以调用apply()时第一个参数是null,下面是使用的例子
function add(num1,num2){
return num1 + num2;
}
var curriedAdd = curry(add,5);
alert(curriedAdd(3)); //8
函数柯里化还常常作为函数绑定的一部分包含在其中,构造成更复杂的bind()
函数
function bind(fn,context){ //接受两个参数,原函数和一个作为执行环境的对象
var args = Array.prototype.slice.call(arguments,2); //从第2项开始以后才是参数
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs); //执行环境不再是null
};
}
Javascript的bind()
方法,也提供函数柯里化功能,只需在this值之后,再传入一个参数即可
var handler = {
message = "Event handle",
handleClick : function(name, event){
alert(this.message + ":" + name + ":" + evnet.type);
}
};
var btn = docuemnt.getElementById("my-btn");
btn.addEventListener("click",handler.handleClick.bind(handler,"my-btn"),false);
防篡改对象
不可扩展对象
Object.preventExtensions()
方法使对象不可添加新属性和方法,但仍可以删除或修改原有属性和方法
var person = {name:"zjw"};
Object.preventExtensions(person);
person.age = 29;
alert(person.age); //undefined
密封对象
Object.seal()
方法使对象不可扩展,且无法删除属性和方法,但可以修改原有属性
var person = {name:"zjw"};
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"zjw"
冻结对象
Object.freeze()
方法使对象冻结,冻结的对象即不可扩展,又是密封的,且不可修改原有属性的值
var person = {name:"zjw"};
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"zjw"
person.name = "wjz";
alert(person.name); //"zjw"
高级定时器
Javascript是运行于单线程的环境中,而setTimeout()
和setInterval()
创建的计时器仅仅是计划代码在未来的某个时间执行,执行时机是不能保证的,因为不同时间可能有其他代码在控制着Javascript的进程。
比如设定了150ms后执行的定时器不代表150ms代码就立刻执行,它表示代码会在150ms后被加入到队列,如果此时,队列中没有其他东西,那么这段代码才会被执行。
重复的定时器
使用setInterval()
创建的定时器确保了定时器代码规则地插入队列中。当队列中没有该定时器的任何代码时,才将定时器代码添加到队列中,避免了定时器代码连续运行无停顿的问题。但这种重复定时器规则有两个问题:(1)某些间隔会被跳过;(2)多个定时器的代码执行之间的间隔可能会被预期小。
为了避免这个问题,可以使用链式setTimeout()
调用,其保证了在前一个定时器执行完之前,不会向队列中插入新的定时器代码,确保了不会有任何缺失的时间间隔。
setTimeout(function(){
//do something
setTimeout(arguments.callee,interval);
},interval);
数据分块
Javascript有一个限制是长时间运行脚本的制约,如果代码运行超过特定的时间或者特定的语句数量就不能让它继续执行。造成脚本长时间运行的原因可能有:过长的、过深嵌套的函数调用或者进行大量的处理的循环。当你的循环不需要同步完成且处理顺序不重要时,可以使用数据分块技术来解决这个问题
//原本的循环
for (var i=0, len=data.length; i < len; i++){
process(data[i]);
}
//数据分块后的循环
setTimeout(function(){
//取出下一条数据并处理
var item = array.shift();
process(item);
//若还有项目,再设置另一个定时器
if (array.length > 0 ){
setTimeout(arguments.callee, 100);
}
},100);
使用定时器进行间歇调用,防止过长的脚本运行时间造成阻塞
函数节流
某些高频率的更改可能会让浏览器崩溃,为了避免这个问题,可使用定时器对该函数节流
function throttle(method,context){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
},100);
}
该函数接受两个参数,要执行的函数和作为执行环境的对象