一、高级函数
安全类型检测
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
作用域安全的构造函数
function Person(name, age, job){
if (this instanceof Person){ // new的时候 this是指向实例的
this.name = name;
this.age = age;
this.job = job;
} else { // 如果不是new来创建 直接return一个new的对象
return new Person(name, age, job);
}
}
如果使用上述写法,当你借用构造函数来实现继承的时候,会被破坏。Person.call(this,'a',2,'student');
,此时的this
的指向子类。所以需要使用组合继承来完成。
惰性载入函数
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
createXHR = function(){
return new ActiveXObject("Microsoft.XMLHTTP");
}
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
第一次调用的时候就已经确定(重写)了之后所有的createXHR()
函数应该的定义,下一次调用createXHR()
的时候,就会直接调用被分配的函数,这样就不用再次执行if
语句了。
第二种惰性载入函数是在声明函数时就指定适当的函数。创建一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。
var createXHR = (function (){
if (typeof XMLHttpRequest != "undefined"){
return function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
return function(){
return new ActiveXObject("Microsoft.XMLHTTP");
}
} else {
return function(){
throw new Error("No XHR object available.");
};
}
})()
函数绑定
函数绑定要创建一个函数,可以在特定的this
环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
function bind(fn, context){
return function(){
return fn.apply(context, arguments);
};
}
function bind(fn, context){
var args = Array.prototype.slice.call(arguments, 2);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
;
}
函数柯里化
函数柯里化(function currying
),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,返回接受处理余下的参数且返回结果的新函数。
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(){
var args = [].slice.call(arguments)
return args.reduce((num1,num2)=>{
return num1+num2
})
}
let curryAdd = curry(add,2,3)
curryAdd(1) // 6
二、防篡改对象
不可扩展对象
使用Object.preventExtensions()
方法可以让你不能再给对象添加属性和方法。
var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29; // 在严格模式会报错
alert(person.age); //undefined
使用Object.isExtensible()
方法还可以确定对象是否可以扩展。
密封的对象
Object.seal()
方法用来密封对象,密封对象不可扩展,而且已有成员的[[Configurable]]
特性将被设置为false
。这就意味着不能删除属性和方法,因为不能使用Object.defineProperty()
把数据属性修改为访问器属性,或者相反。属性值是可以修改的。
var person = { name: "Nicholas" };
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
person.name = "jack"
alert(person.name); // "jack"
alert(Object.isExtensible(person)); //false 不可扩展
alert(Object.isSealed(person)); //true 密封对象
使用Object.isSealed()
方法可以确定对象是否被密封了,被密封的对象不可扩展,因此使用Object.istExtensible()
方法返回false
冻结的对象
最严格的防篡改级别是冻结对象(frozen object
)。冻结的对象既不可扩展,又是密封的,而且对象数据属性的[[Writable]]
特性会被设置为false
。但是如果定义[[Set]]
函数,访问器属性仍然是可写的。Object.freeze()
方法可以用来冻结对象。
var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined 不能扩展
delete person.name;
alert(person.name); //"Nicholas" 不能删除
person.name = "Greg";
alert(person.name); //"Nicholas" 没有set 不能修改
Object.isFrozen()
方法用于检测冻结对象。因为冻结对象既是密封的又是不可扩展的,所以用Object.isExtensible()
和Object.isSealed()
检测冻结对象将分别返回false
和true
。
三、高级定时器
除了主JavaScript
执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接收到某个Ajax
响应时,回调函数的代码会被添加到队列。在JavaScript
中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
设定一个150ms
后执行的定时器不代表到了150ms
代码就立刻执行,它表示代码会在150ms
后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就会被执行,如果有,代码可能明显地等待更长时间(等待事件循环中的同步代码执行完之后)才执行。
重复定时器
使用setInterval()
创建的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。幸好,JavaScript
引擎够聪明,能避免这个问题。当使用setInterval()
时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。

可以使用链式调用setTimeout()
来避免,这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。
setTimeout(function(){
//处理中
setTimeout(arguments.callee, interval);
}, interval);
Yielding Processes
运行在浏览器中的JavaScript
都被分配了一个确定数量的资源。不同于桌面应用往往能够随意控制他们要的内存大小和处理器时间,JavaScript
被严格限制了,以防止恶意的Web
程序员把用户的计算机搞挂了。其中一个限制是长时间运行脚本的制约,如果代码运行超过特定的时间或者特定语句数量就不让它继续执行。如果代码达到了这个限制,会弹出一个浏览器错误的对话框,告诉用户某个脚本会用过长的时间执行,询问是允许其继续执行还是停止它。
当你发现某个循环占用了大量时间,同时对于是否必须同步,是否必须按顺序完成,你的回答都是“否”,那么你就可以使用定时器分割这个循环。这是一种叫做数组分块( array chunking
)的技术,小块小块地处理数组,通常每次一小块。
基本的思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
function chunk(array, process, context){
setTimeout(function(){
//取出下一个条目并处理
var item = array.shift();
process.call(context, item);
//若还有条目,再设置另一个定时器
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
定时器的时间间隔设置为了100ms
,使得JavaScript
进程有时间在处理项目的事件之间转入空闲。你可以根据你的需要更改这个间隔大小,不过100ms
在大多数情况下效果不错。
函数防抖
在多次触发时,只在调用之后,在指定的时间间隔内不再次触发的情况下执行一次。实际开发多用于输入搜索,停止输入多少秒之后进行请求数据。
你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件
function fn(method,delay) {
let timeId= null;
return function(){
let context = this;
clearTimeout(timeId);
timeId= setTimeout(function(){
method.call(context);
},delay);
}
}
// 触发了多次 都只会在最后一次调用 设置的间隔之后执行一次
函数节流
如果你持续触发事件,每隔一段时间,只执行一次事件。
function fn(method,delay) {
var timeId= null;
return function(){
let context = this;
if(!timeId){ // 定时器不存在时才执行
timeId = setTimeout(()=>{
method.call(context);
clearTimeout(timeId); // 本次的定时器跑完之后 才清楚定时器让其为null
},delay);
}
}
}
function fn2(method,delay) {
let initTime = Date.now()
return function(){
let context = this;
let doTime = Date.now();
if(doTime - initTime >= delay){
method.call(context);
initTime = Date.now()
}
}
}
四、自定义事件(观察者模式)
观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。该模式的一个关键概念是主体并不知道观察者的任何事情,也就是说它可以独自存在并正常运作即使观察者不存在。从另一方面来说,观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM
上时,DOM
元素便是主体,你的事件处理代码便是观察者。
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function(type, handler){ // 注册事件
if (typeof this.handlers[type] == "undefined"){
this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire: function(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;
}
}
handlers.splice(i, 1);
}
}
};
function handleMessage(event){
alert("Message received: " + event.message);
}
//创建一个新对象
var target = new EventTarget();
//添加一个事件处理程序
target.addHandler("message", handleMessage);
//触发事件
target.fire({ type: "message", message: "Hello world!"});
//删除事件处理程序
target.removeHandler("message", handleMessage);
五、拖放 + 自定义函数
var DragDrop = function(){ // 自执行函数
var dragdrop = new EventTarget(), // 观察者对象实例
dragging = null;
diffX = 0;
diffY = 0;
function handleEvent(event){
//获取事件和目标
var target = event.target;
//确定事件类型
switch(event.type){
case "mousedown": // 鼠标按下时 获取鼠标处于目标的哪个位置 记录位置
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
// 鼠标移动时触发 注册的dragStart事件
dragdrop.fire({type:"dragstart", target: dragging,
x: event.clientX, y: event.clientY});
}
break;
case "mousemove": // 鼠标按下后 移动时 移动目标 (鼠标位置-鼠标处于目标的位置)
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
// 鼠标移动时 触发自定义的drag事件
dragdrop.fire({type:"drag", target: dragging,
x: event.clientX, y: event.clientY});
}
break;
case "mouseup": // 鼠标放开 注销事件
// 鼠标放开 触发自定义的dragend事件
dragdrop.fire({type:"dragend", target: dragging,
x: event.clientX, y: event.clientY});
dragging = null;
break;
}
};
//公共接口
dragdrop.enable: function(){
document.addEventListener('mousedown',handleEvent);
document.addEventListener('mousemove',handleEvent)
document.addEventListener('mouseup',handleEvent)
},
dragdrop.disable: function(){
document.removeEventListener('mousedown',handleEvent);
document.removeEventListener('mousemove',handleEvent)
document.removeEventListener('mouseup',handleEvent)
}
}
return dragdrop
}();
// 注册事件
DragDrop.addHandler("dragstart", function(event){
console.log("Started dragging ")
});
DragDrop.addHandler("drag", function(event){
console.log( "Draging ")
});
DragDrop.addHandler("dragend", function(event){
console.log(" dragend")
});
// 此时的DragDrop拥有自定义拖动相关事件了 且能够可以被拖动的元素跟随鼠标移动
DragDrop.enable(); // 启用拖动
DragDrop.disable(); // 禁用拖动
网友评论