本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-06-23
function Person(name, age, job){
if (this instanceof Person){ // new的时候 this是指向实例的
this.name = name;
this.age = age;
this.job = job;
} else {
// 针对直接Person() 直接return一个new的对象
return new Person(name, age, job);
}
}
如果使用上述写法,当你借用构造函数来实现继承的时候,会被破坏。
Person.call(this,'jack', 15, '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
),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
具体请查看:函数柯里化
使用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" 没有setter 不能修改
Object.isFrozen()
方法用于检测冻结对象。因为冻结对象既是密封的又是不可扩展的,使用Object.isExtensible()
和Object.isSealed()
检测冻结对象将分别返回false
和true
。
除了主JavaScript
执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。随着页面在其生命周期中的推移,代码会按照执行顺序添加入队列。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接收到某个Ajax
响应时,回调函数的代码会被添加到队列。在JavaScript
中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行。
设定一个150ms
后执行的定时器不代表到了150ms
代码就立刻执行,它表示代码会在150ms
后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就会被执行,如果有,代码可能明显地等待更长时间(等待事件循环中的同步代码执行完之后)才执行。
使用setInterval()
创建的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。幸好,JavaScript
引擎够聪明,能避免这个问题。当使用setInterval()
时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。
可以使用链式调用setTimeout()
来避免,这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔。
var interval = 1000;
setTimeout(function(){
// do something
console.log(1);
//处理中 arguments.callee 即该匿名函数的引用
setTimeout(arguments.callee, interval);
}, interval);
运行在浏览器中的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秒之后再执行。
function debounce(method,delay) {
let timeId = null;
return function(){
let context = this;
clearTimeout(timeId);
timeId= setTimeout(function(){
method.call(context);
},delay);
}
}
// 触发了多次 都只会在最后一次调用 设置的间隔之后执行一次
节流:在制定间隔内任务只执行1次。节流的原理是不管你在一段时间内如何不停地触发事件,只要设置了节流,就会每隔一段时间执行一次。
function throttle(method,delay) {
var timeId = null;
return function(){
let context = this;
if (!timeId) { // 定时器不存在时才执行
timeId = setTimeout(() => {
method.call(context);
clearTimeout(timeId); // 本次的定时器跑完之后 才清楚定时器让其为null
}, delay);
}
}
}
function throttle2(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(e){ // 触发 e { type: string ; message: string } 调用者应有此类型限制
if (!e.target){
e.target = this;
}
if (this.handlers[e.type] instanceof Array){
var handlers = this.handlers[e.type];
for (var i=0, len=handlers.length; i < len; i++){
handlers[i](e);
}
}
},
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){ // 此event对象上 应还有type、target属性
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(); // 禁用拖动