Fork me on GitHub

《JavaScript高级程序设计》—— 高级技巧

摘要:

博客开了几个月了也没写什么,想着还是随手写一点吧,也有助于自己的加深理解。最近又在看《JavaScript高级程序设计》,刚好看到第22章的高级技巧,就随手码下来吧。

本文内容由《JavaScript高级程序设计》书上的P596 - P625 和 本人对这些技巧的理解提供。

适合人群:如果你刚好在看这本书,或者想特高JS代码的性能优化,减少代码耦合,都可以看本文章。当然,你还需要有一定的JS语法基础。那么,开始吧:


一、高级函数

1、安全的类型检测

JavaScript内置的类型检测机制并非完全可靠。比如Safari(直至第4版)在对正则表达式应用typeof操作符时会返回function,instanceof操作符在存在多个全局作用域(像一个页面包含多个frame)的情况下,也是问题多多,比如:

1
var isArray = arr instanceof Array;

如果arr是在另一个frame中定义的数组。那么这里就会返回false。在检测某个对象到底是原生对象还是开发人员自定义的对象的时候。也会有问题。上述问题解决方法都一样:

1
console.log(Object.prototype.toString.call(arr)); // "[Object Array]"

由于原生数组的构造函数名与全局作用域无关,因此使用toString就能保证返回一致的值,利用这点可以创建如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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]";
}
function isNativeJSON(value) {
return Object.prototype.toString.call(value) == "[Object JSON]";
}

在开发中能够区分原生与非原生对象非常重要。只有这样才能确切知道某个对象到底有哪些功能。

2、作用域安全的构造函数

直接来看看作用域不安全的构造函数吧

1
2
3
4
5
function Person(name) {
this.name = name;
}
var person = Person('Tom');
console.log(window.name); // "Tom"

上面问题出在当没有使用new操作符来调用该构造函数的情况上。由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window上,导致错误对象属性的意外增加。

解决的思路是 作用域安全的构造函数在进行任何更改前,首先确认this对象是正确类型的实例。如果不是那么会创建新的实例并返回。如下:

1
2
3
4
5
6
7
8
9
10
function Person(name) {
if(this instanceof Person) {
this.name = name;
} else {
return new Person(name);
}
}
var person1 = Person("Tom");
console.log(window.name); // ""
console.log(person1.name); // "Tom"

这段代码中添加了一个检查并确保this对象是Person实例。最后的结果是,调用Person构造函数无论是否使用new操作符,都会返回一个Person的新实例。

这样处理也有坏处,就是构造函数窃取模式的继承该实例是无效的。比如:

1
2
3
4
5
6
function People(sex) {
Person.call(this,'Tom'); // 构造函数窃取模式 —— 继承Polygon的name属性 不过在这种作用域安全情况下不可用
this.sex = sex;
}
var people1 = new People('boy');
console.log(people1.name); // undefined

在这里由于Person构造函数作用域是安全的,this对象并非Person实例,所以会创建并返回一个新的Person对象,所以People构造函数中的this对象并没有得到增长,也就不会有name属性。

解决上面的问题,使用原型链继承即可:

1
2
3
4
5
6
function People(sex) {
this.sex = sex;
}
People.prototype = new Person('Tom');
var people1 = new People('boy');
console.log(people1.name); // "Tom"

多个程序猿在同一个页面上写JavaScript代码的环境中,作用域安全构造函数就很有用了。推荐作用域安全的构造函数作为最佳实践。

3、惰性载入函数

因为浏览器之间行为的差异,多数JavaScript代码包含了大量的if语句,将执行引导到正确的代码中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createXHR() {
if(typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
} else if(typeof ActiveXObject != "undefined") {
if(typeof arguments.callee.activeXString != "string") {
var version = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i,
len;
for(i = 0, len = version.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = version[i];
break;
} catch(ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available");
}
}

每次调用createXHR(),它都要对浏览器所支持的能力进行仔细检查。每次调用该函数都是这样,即使调用时分支的结果都不变,如果浏览器内置XHR,那么它就一直支持了,那么这种测试就变得没必要。即使只有一个if语句的代码,也肯定要比没有if语句的慢。

解决方案就是称为惰性载入的技巧。书上写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 version = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i,
len;
for(i = 0, len = version.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = version[i];
break;
} catch(ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
} else {
createXHR = function() {
throw new Error("No XHR object available");
}
}
}

这是是把函数表达式写在函数内部,我认为把函数表达式写在外部即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
createXHR = function() { // 这是函数表达式写法
if(typeof XMLHttpRequest != "undefined") {
return new XMLHttpRequest();
} else if(typeof ActiveXObject != "undefined") {
if(typeof arguments.callee.activeXString != "string") {
var version = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
i,
len;
for(i = 0, len = version.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = version[i];
break;
} catch(ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available");
}
}

这两种写法都可以在执行createXHR方法的时候执行第一遍后,后面执行就不会再进行if判断。如果有需要函数第一次执行也不产生if判断,那么就在函数声明的时候执行。

惰性载入函数的优点是只在执行分支代码的时候牺牲一点儿性能。至于那种方式更合适,就要看你的具体需求而定了。

4、函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境,看下面栗子:

1
2
3
4
5
6
7
8
var person = {
name: "Tom",
talk: function() {
con('I am ' + this.name);
}
}
var btn = document.getElementById("bindBtn");
btn.addEventListener('click', person.talk);

上面代码问题看似点击按钮的时候会打印 “I am Tom”, 实际显示的是 “I am “。

这个问题在于没有保存person.talk()的环境,所以this对象最后是指向了Dom按钮(在IE8中,this指向window)。

解决这个问题,可以使用一个闭包来解决,看下面代码(以下代码只显示修改的部分):

1
2
3
btn.addEventListener('click', function() {
person.talk();
});

这个解决方案在click事件处理程序内使用了一个闭包直接调用person.talk(); 这只是这段代码的解决方案,创建多个闭包可能会令代码变得难于理解和调试。

因此,很多JS库实现了一个可以将函数绑定到指定环境的函数。这个函数一般都叫bind()。

实现一个比较基本的bind()函数:

1
2
3
4
5
function bind(fn, context) {
return function() {
return fn.apply(context, arguments); // 这里使用的arguments对象是内部函数的,并非bind()的。
}
}

bind()中创建了一个必报,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。

结合上面的代码结合使用自定义bind(方法):

1
btn.addEventListener('click', bind(person.talk, person));

其实在ES5已经为所有函数定义了一个原生bind()方法,使用方法如下:

1
btn.addEventListener('click', person.talk.bind(person));

只要是将某个函数指针(环境)以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。

主要用于事件处理程序以及setTimeout()和setInterval()。

然而,被绑定函数与普通函数相比有更多的开销,需要更多的内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。

5、函数柯里化

与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好了一个或多个参数的函数。

函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数,看栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1); // 这里截取函数之后的参数
return function() {
var varArgs = Array.prototype.slice.call(arguments); // 这里接受curryAdd的参数
var finalArgs = args.concat(varArgs);
return fn.apply(null, finalArgs);
}
}
function add(num1, num2) {
return num1 + num2;
}
var curryAdd = curry(add, 5);
console.log(curryAdd(1)); // 6

这里创建第一个参数绑定为5的add()柯里化版本。你也可以像下面这样给出所有的函数参数:

1
2
var curryAdd = curry(add, 5, 1);
console.log(curryAdd(1)); // 6 因为add()没有接受第三个参数,所以还是6

函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数。以ES5的bind()方法举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function bind(fn, context) {
var args = Array.prototype.slice.call(arguments, 2); //这里需要改为2,获取函数和环境后面的参数
return function() {
var varArgs = Array.prototype.slice.call(arguments); //这里从第三个参数开始接受
var finalArgs = args.concat(varArgs);
return fn.apply(context, finalArgs);
}
}
var person = {
name: "Tom",
talk: function(message1, message2) {
console.log('I am ' + this.name + ',' + message1 + ',' + message2 );
}
}
var talking = person.talk.bind(person, " I am talking ") // 这里创建一个第一个参数绑定为“ I am talking ”的函数
var btn = document.getElementById("bindBtn");
btn.addEventListener('click', function() {
talking('I am running'); // 这里就任意添加talking后面的参数
});

这样每次单机按钮就会弹出相应参数的字符串。

PS:这里有个坑呀。btn.addEventListener(‘click’, talking(‘I am running’)); 这样点击按钮是不会有反应的。

总的来说,柯里化的作用可以使代码模块化,减少耦合增强其可维护性。柯里化函数和绑定函数提供了强大的动态函数创建功能,两者不应滥用,只在必要的时候用。

二、防篡改对象

在编写JavaScript库中,因为JS共享的特性,开发人员很可能会意外地修改别人代码。

ES5新增了几个方法,通过它们可以指定对象的行为。

1、不可拓展对象 Object.preventExtensions()

1
2
3
4
5
6
7
8
var person1 = { name: "Tom" };
person1.age = 20;
Object.preventExtensions(person1);
person1.age = 30;
person1.sex = "boy"
console.log(person1.age, person1.sex); // 30, undefined
delete person1.age;
console.log(person1.age) // undefined

可以看出调用 Object.preventExtensions() 方法后,就不能给person1对象添加新的属性和方法。但是仍然可以修改和删除已有的成员。

2、密封的对象 Oject.seal()

密封对象不可拓展,而且已有的成员[[configurable]]特性将被设置为false。具体功能还是直接看代码:

1
2
3
4
5
6
7
8
9
var person1 = { name: "Tom" };
person1.age = 20;
Object.seal(person1);
person1.sex = "boy";
console.log(person1.boy); // undefined
delete person1.name;
console.log(person1.name); // "Tom"
person1.age = 23;
console.log(person1.age); // 23

除了不能增删外,只有修改操作是可以进行的。

3、冻结的对象 Object.freeze()

最严格的防篡改级别是冻结对象。冻结对象既不可拓展,又是密封的。而且每个对象数据属性[[Writable]]特性会被设置为false。

1
2
3
4
5
6
7
8
9
10
11
var person1 = {
name: "Tom"
};
person1.age = 20;
Object.freeze(person1);
person1.sex = "boy";
console.log(person1.boy); // undefined
delete person1.name;
console.log(person1.name); // "Tom"
person1.age = 23;
console.log(person1.age); // 20

可以看出冻结的对象什么也无法修改删除,这样冻结(或密封)主要的库就能够防止这些问题的发生。

三、高级定时器

1、setTimeout()

首先,JavaScript是运行在单线程环境中,而定时器只是表示指定间隔多少时间把代码添加到队列内。

JS队列执行代码方式还是用代码体验一下吧:

1
2
3
4
5
6
7
8
9
var btn = document.getElementById("bindBtn");
btn.onclick = function() {
timer1 = setTimeout(function() {
console.log('执行第一个定时器'); // 第一个执行
}, 1000);
timer2 = setTimeout(function() {
console.log('执行第二个定时器'); // 第二个执行
}, 2000);
};

点击按钮后可以发现timer1执行,再过一秒后timer2也执行,一切正常。因为在事件处理程序里面没有其他代码要添加到队列里,效果比较不明显。

那么我们添加点其他代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var btn = document.getElementById("bindBtn");
btn.onclick = function() {
console.time('time:');
for (var i=0,a=0;i<99999999;i++) {
a = Math.floor(Math.random()*10000);
};
a=null;
console.timeEnd('time:');
timer1 = setTimeout(function() {
console.log('执行第一个定时器'); // 第一个执行
}, 1000);
timer2 = setTimeout(function() {
console.log('执行第二个定时器'); // 第二个执行
}, 2000);
};

现在明显感受到定时器输出的晚了几秒。
解析一下:点击后,队列中执行for循环,等待for循环结束后,再把timer1和timer2的代码放入队列相隔设置好的时间后执行。这样一来,就好理解多了。

2、setInterval()

1
2
3
4
5
6
7
8
9
10
11
12
13
var btn = document.getElementById("bindBtn");
btn.onclick = function() {
var b = 0;
timer1 = setInterval(function() {
console.time('time:');
for(var i = 0, a = 0; i < 99999999; i++) {
a = Math.floor(Math.random() * 10000);
};
a = null;
console.log('第' + ++b + '次定时器执行');
console.timeEnd('time:'); // 基本上每次都间隔2.5秒
}, 2000);
};

这个例子中第一个定时器是在2000ms处添加到队列中,但是代码块需要2.5s左右时间执行,所以第一个打印记录时间是4.5s。而在4s的时候本来是应该执行第二个定时器的代码,这时候就会没有延迟的立即执行第二个定时器代码在6s的时候显示”第2次定时器执行”。

书上有说会缺失间隔的代码,不知道是执行的代码时间不够长还是怎样,执行起来并没有。了解详情的还望指教一下。

3、Yielding Processes

JavaScript在浏览器中有一个限制是长时间运行脚本的制约,会弹出一个浏览器错误的对话框,告诉用户某个脚本会用过长的时间执行,询问是允许其继续执行还是停止它。

脚本长时间运行问题通常是两个原因之一造成的:过深的嵌套、过长的函数调用或者是进行大量处理的循环。

其实就是当你发现某个循环占用了大量时间,如果这个数据的处理不会造成其他运行的阻塞。

那么你就可以用定时器分割这个循环。这是一种叫“数组分块”的技术,小块小块地处理数组。比如:

1
2
3
4
5
6
7
8
9
10
11
12
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);

数组分块的重要性在于它可以将多个项目的处理在执行队列上分开。

一旦某个函数需要花50ms以上的时间完成,那么最好看看能否将任务分割为一系列可以使用定时器的小任务。

4、函数节流

函数节流的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清楚前一次定时器并设置另一个。

以下是该模式的基本形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var person = {
runTime: null,
// 实际进行处理的方法
run: function() {
//实际执行的代码
},
// 初始处理调用的方法
readyRun: function() {
clearTimeout(this.runTime);
var that = this;
this.timeoutId = setTimeout(function() {
that.run();
}, 1000);
}
}

这段代码中,拟人化的创建了一个人,有两个方法:run()和readyRun()。前者是实际进行的动作,后者是初始化动作所必须调用的。

当调用readyRun(),第一步是清楚存好的runTime,来阻止之前的调用被执行。然后创建一个新的定时器调用run()。由于setTimeout中用到的函数环境总是window,所以有必要保存this的引用方便使用。

这样的好处是即使1000ms内调用了readRun(),run()也只会调用一次。(毕竟人不可能一秒跑那么多步的好嘛!)

节流在resize,scorll事件中是最常用的。如果你基于该事件来改变页面布局的话,最好控制处理的频率,以确保浏览器不会再极短的时间内进行过多的计算。

这里先设计一个函数封装一下这个方法,方便以后常用:

1
2
3
4
5
6
function throttle(fn, delay, context) {
clearTimeout(fn.throTime); // 默认你给需要执行的函数里都有throTime属性
fn.throTime = setTimeout(function() {
fn.call(context); // 函数在指定环境执行
}, delay);
}

写一个滑动滚轮改变盒子大小的方法:

1
2
3
4
5
6
7
8
9
function changeBg() {
var div = document.getElementById("dragTarget");
div.style.width = div.offsetWidth + 10 + 'px';
div.style.height = div.offsetHeight + 10 + 'px';
};
下面使用优化一下onscroll的事件处理程序
window.onscroll = function() {
throttle(changeBg, 100);
}

这里多数情况下用户使感觉不到变化的,可是这大大节省了浏览器的计算。
只要代码是周期性执行的,都应该使用节流,并且适当控制速率。

四、自定义事件

如果每个对象都有对其他所有对象的引用,那么整个代码就会紧密耦合,同时维护也变得很困难,因为对某个对象的修改也会影响到其他对象。使用自定义事件有助于解耦相关对象,保持功能的隔绝。

实际上,在很多情况下,触发事件的代码和舰艇事件的代码是完全分离的。

自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式可以如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function EventTarget() {
this.handlers = {}; // 存放事件名的对象
}
EventTarget.prototype = {
con: function() { // 输出当前存放的所有事件名
console.log(JSON.stringify(this.handlers));
},
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);
}
} else {
console.log('没有注册过该事件');
}
},
removeHandler: function(type, handler) { // 移除指定事件处理程序
if(this.handlers[event.type] instanceof Array) {
var handlers = this.handlers[type];
for(var i = 0, len = handlers.length; i < len; i++) {
if(handlers[i] === handler) { // 找到事件处理程序的位置,跳出循环
break;
}
}
} else {
console.log('没有注册过该事件');
}
handlers.splice(i, 1); // 删除事件处理程序
}
}

然后,使用EventTarget类型的自定义事件可以如下使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function handleMessage(event) {
console.log("Message received" + event.message);
}
var dom = new EventTarget();
dom.addHandler("dianJi",handleMessage);
dom.fire({type:'dianJi', message: 'I am Click'}); // "Message receivedI am Click"
dom.removeHandler("dianJi",handleMessage);
dom.fire({type:'dianJi', message: 'I am Click'}); // Error: Cannot read property 'type' of undefined"
其他对象可以继承这种自定义对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = new EventTarget();
var person = new Person('Tom', 20);
function handleMessage(event) {
console.log("I talk " + event.message);
}
person.addHandler("say", handleMessage);
person.fire({type:'say', message:'I am Person'}); // "I talk I am Person"

五、拖放

拖放是一种非常流行的用户界面模式。他的概念很简单:点击某个对象,并按住鼠标按钮不放,将鼠标移动到另一个区域,然后释放鼠标按钮将对象“放”在这里。

简单的拖放界面可用一下代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var DragDrop = function() {
var dragging = null, // 拖放对象
diffX = 0,
diffY = 0;
function handleEvent(event) {
// 获取事件和目标
event = event || window.event;
var target = event.target || window.event.target;
switch(event.type) {
case "mousedown":
console.log(target)
if(target.className.indexOf("draggable") > -1) {
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
}
break;
case "mousemove":
if(dragging !== null) {
dragging.style.left = event.clientX - diffX + 'px';
dragging.style.top = event.clientY - diffY + 'px';
}
break;
case "mouseup":
dragging = null;
break;
}
};
return {
enable: function() {
document.addEventListener('mousedown', handleEvent);
document.addEventListener('mousemove', handleEvent);
document.addEventListener('mouseup', handleEvent);
},
disable: function() {
document.removeEventListener('mousedown', handleEvent);
document.removeEventListener('mousemove', handleEvent);
document.removeEventListener('mouseup', handleEvent);
}
}
}
DragDrop().enable();

DragDrop对象封装了拖放的所有基本功能。拖放的时候回自动针对所有包含“draggable”类的元素启用。

为了元素能被拖放,它必须是绝对定位的。

结合前面的自定义事件完善该功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var dragdrop = new EventTarget();
var DragDrop = function() {
var dragging = null,
diffX = 0,
diffY = 0;
function handleEvent(event) {
event = event || window.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;
// 这里触发自定义事件,比如:
dragdrop.fire({
type: "dragStart",
target: dragging
});
}
break;
case "mousemove":
if(dragging !== null) {
dragging.style.left = event.clientX - diffX + 'px';
dragging.style.top = event.clientY - diffY + 'px';
// 这里触发自定义事件,比如:
dragdrop.fire({
type: "dragging",
target: dragging
});
}
break;
case "mouseup":
// 这里触发自定义事件,比如:
dragdrop.fire({
type: "dragend",
target: dragging
});
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) {
event.target.innerHTML = "准备移动!";
});
DragDrop().addHandler("dragging", function(event) {
event.target.innerHTML = "正在移动ing!";
});
DragDrop().addHandler("dragend", function(event) {
event.target.innerHTML = "移动结束!";
});
DragDrop().enable();

其实这里有个坑跟书上的不一样,就是new EventTarget()要在外一层new。


以上就是《JavaScript高级程序设计》书上的高级技巧和本人对这些知识点的一些理解,所码的文章。

本文经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。

-------------本文结束感谢您的阅读-------------
分享